blob: 42979396e88482fb655be488ccd524ef4001a778 [file] [log] [blame]
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +03001package com.mirantis.mcp
2
Sergey Kolekonov74a6b6e2019-06-28 11:45:47 +04003import org.jfrog.hudson.pipeline.common.types.ArtifactoryServer
4import org.jfrog.hudson.pipeline.common.types.buildInfo.BuildInfo
Sergey Kulanov91d8def2016-11-15 13:53:17 +02005
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +03006/**
7 * Return string of mandatory build properties for binaries
8 * User can also add some custom properties.
9 *
10 * @param customProperties a Array of Strings that should be added to mandatory props
11 * in format ["prop1=value1", "prop2=value2"]
12 * */
13def getBinaryBuildProperties(ArrayList customProperties) {
14 def namespace = "com.mirantis."
15 def properties = [
Sergey Kulanovc70f1c22016-11-16 13:05:20 +020016 "buildName=${env.JOB_NAME}",
17 "buildNumber=${env.BUILD_NUMBER}",
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030018 "gerritProject=${env.GERRIT_PROJECT}",
19 "gerritChangeNumber=${env.GERRIT_CHANGE_NUMBER}",
20 "gerritPatchsetNumber=${env.GERRIT_PATCHSET_NUMBER}",
21 "gerritChangeId=${env.GERRIT_CHANGE_ID}",
22 "gerritPatchsetRevision=${env.GERRIT_PATCHSET_REVISION}"
23 ]
24
25 if (customProperties) {
26 properties.addAll(customProperties)
27 }
28
29 def common = new com.mirantis.mcp.Common()
30
31 return common.constructString(properties, namespace, ";")
32}
33
34/**
Kirill Mashchenko1d225c22018-06-19 13:52:17 +030035 * Get URL to artifact(s) by properties
36 * Returns String(s) with URL to found artifact or null if nothing
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030037 *
38 * @param artifactoryURL String, an URL to Artifactory
39 * @param properties LinkedHashMap, a Hash of properties (key-value) which
40 * which should determine artifact in Artifactory
Kirill Mashchenko1d225c22018-06-19 13:52:17 +030041 * @param onlyLastItem Boolean, return only last URL if true(by default),
42 * else return list of all found artifact URLS
Sergey Kolekonov54c44842019-06-17 19:25:52 +040043 * @param repos ArrayList, a list of repositories to search in
Kirill Mashchenko1d225c22018-06-19 13:52:17 +030044 *
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030045 */
Sergey Kolekonov54c44842019-06-17 19:25:52 +040046def uriByProperties(String artifactoryURL, LinkedHashMap properties, Boolean onlyLastItem=true, ArrayList repos=[]) {
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030047 def key, value
48 def properties_str = ''
49 for (int i = 0; i < properties.size(); i++) {
50 // avoid serialization errors
Kirill Mashchenko56c8ff32018-06-28 03:01:34 +030051 key = properties.entrySet().toArray()[i].key.trim()
52 value = properties.entrySet().toArray()[i].value.trim()
53 properties_str += /${key}=${value}&/
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030054 }
Sergey Kolekonov54c44842019-06-17 19:25:52 +040055 def repos_str = (repos) ? repos.join(',') : ''
56 def search_url
57 if (repos_str) {
58 search_url = "${artifactoryURL}/api/search/prop?${properties_str}&repos=${repos_str}"
59 } else {
60 search_url = "${artifactoryURL}/api/search/prop?${properties_str}"
61 }
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030062
Kirill Mashchenko56c8ff32018-06-28 03:01:34 +030063 def result = sh(script: /curl -X GET '${search_url}'/,
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030064 returnStdout: true).trim()
65 def content = new groovy.json.JsonSlurperClassic().parseText(result)
66 def uri = content.get("results")
67 if (uri) {
Kirill Mashchenko1d225c22018-06-19 13:52:17 +030068 if (onlyLastItem) {
69 return uri.last().get("uri")
70 } else {
71 res = []
72 uri.each {it ->
73 res.add(it.get("uri"))
74 }
75 return res
76 }
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030077 } else {
78 return null
79 }
80}
81
Kirill Mashchenko1d225c22018-06-19 13:52:17 +030082
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030083/**
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030084 * Set properties for artifact in Artifactory repo
85 *
86 * @param artifactUrl String, an URL to artifact in Artifactory repo
87 * @param properties LinkedHashMap, a Hash of properties (key-value) which
88 * should be assigned for choosen artifact
89 * @param recursive Boolean, if artifact_url is a directory, whether to set
90 * properties recursively or not
91 */
92def setProperties(String artifactUrl, LinkedHashMap properties, Boolean recursive = false) {
93 def properties_str = 'properties='
94 def key, value
95 if (recursive) {
96 recursive = 'recursive=1'
97 } else {
98 recursive = 'recursive=0'
99 }
Alexander Evseevbd40ef92017-10-18 12:24:45 +0300100 properties_str += properties.collect({"${it.key}=${it.value}"}).join(';')
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300101 def url = "${artifactUrl}?${properties_str}&${recursive}"
102 withCredentials([
103 [$class : 'UsernamePasswordMultiBinding',
104 credentialsId : 'artifactory',
105 passwordVariable: 'ARTIFACTORY_PASSWORD',
106 usernameVariable: 'ARTIFACTORY_LOGIN']
107 ]) {
108 sh "bash -c \"curl -X PUT -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} \'${url}\'\""
109 }
110}
111
112/**
Sergey Kolekonov76c17f52019-09-09 16:55:01 +0400113 * Create an empty directory in Artifactory repo
114 *
115 * @param artifactoryURL String, an URL to Artifactory
116 * @param path String, a path to the desired directory including repository name
117 * @param dir String, desired directory name
118 */
119def createDir (String artifactoryURL, String path, String dir) {
120 def url = "${artifactoryURL}/${path}/${dir}/"
121 withCredentials([
122 [$class : 'UsernamePasswordMultiBinding',
123 credentialsId : 'artifactory',
124 passwordVariable: 'ARTIFACTORY_PASSWORD',
125 usernameVariable: 'ARTIFACTORY_LOGIN']
126 ]) {
127 sh "bash -c \"curl -X PUT -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} \'${url}\'\""
128 }
129}
130
131/**
Sergey Kolekonovce616712019-09-10 16:09:23 +0400132 * Move/copy an artifact or a folder to the specified destination
Sergei Otpushchennikove204aac2025-12-02 09:36:04 +0300133 *
134 * @param artifactoryURL String, an URL to Artifactory
135 * @param sourcePath String, a source path to the artifact including repository name
136 * @param dstPath String, a destination path to the artifact including repository name
137 * @param copy boolean, whether to copy or move the item, default is move
138 * @param dryRun boolean, whether to perform dry run on not, default is false
139 */
140def moveItem (String artifactoryURL, String sourcePath, String dstPath, boolean copy = false, boolean dryRun = false) {
141 def url = "${artifactoryURL}/api/${copy ? 'copy' : 'move'}/${sourcePath}?to=/${dstPath}&dry=${dryRun ? '1' : '0'}"
142 def http = new com.mirantis.mk.Http()
143 return http.doPost(url, 'artifactory')
144}
145
146/**
147 * Move/copy an artifact or a folder to the specified destination
Roman Vialovd36c1c02025-11-21 12:49:11 +0600148 * Uses curl to download/upload/delete files since /api/copy and /api/move are not supported
Sergey Kolekonovce616712019-09-10 16:09:23 +0400149 *
150 * @param artifactoryURL String, an URL to Artifactory
151 * @param sourcePath String, a source path to the artifact including repository name
152 * @param dstPath String, a destination path to the artifact including repository name
153 * @param copy boolean, whether to copy or move the item, default is move
154 * @param dryRun boolean, whether to perform dry run on not, default is false
155 */
Sergei Otpushchennikove204aac2025-12-02 09:36:04 +0300156def moveItemNew (String artifactoryURL, String sourcePath, String dstPath, boolean copy = false, boolean dryRun = false, String credentialsId = 'artifactory') {
Roman Vialovd36c1c02025-11-21 12:49:11 +0600157 def respCode = 200
158 def respText = ''
159
160 withCredentials([
161 [$class : 'UsernamePasswordMultiBinding',
162 credentialsId : credentialsId,
163 passwordVariable: 'ARTIFACTORY_PASSWORD',
164 usernameVariable: 'ARTIFACTORY_LOGIN']
165 ]) {
166 try {
167 // Check if source is a file or directory
168 def storageUrl = "${artifactoryURL}/api/storage/${sourcePath}"
169 def storageResult = sh(script: """
170 set +e
171 response=\$(curl -s -w "\\n%{http_code}" -X GET -u \${ARTIFACTORY_LOGIN}:\${ARTIFACTORY_PASSWORD} '${storageUrl}' 2>&1)
172 echo "\$response"
173 """, returnStdout: true).trim()
174
175 def storageLines = storageResult.split('\n')
176 def storageCode = storageLines.length > 0 && storageLines[-1] ==~ /^\d{3}$/ ? storageLines[-1].toInteger() : 404
177 def storageBody = storageLines.length > 1 ? storageLines[0..-2].join('\n') : ''
178
179 if (storageCode != 200) {
180 return [storageCode, storageBody ?: "Source path not found: ${sourcePath}"]
181 }
182
183 def storageInfo = new groovy.json.JsonSlurperClassic().parseText(storageBody)
184 def isDirectory = storageInfo.get('children') != null
185
186 if (dryRun) {
187 respText = "DRY RUN: Would ${copy ? 'copy' : 'move'} ${isDirectory ? 'directory' : 'file'} from ${sourcePath} to ${dstPath}"
188 return [200, respText]
189 }
190
191 if (isDirectory) {
192 // Handle directory: recursively copy all files using checksum deploy
193 def filesToCopy = getDirectoryFiles(artifactoryURL, sourcePath, credentialsId)
194 def errors = []
195
196 filesToCopy.each { filePath ->
197 def relativePath = filePath.replaceFirst("^${sourcePath}/", '')
198 def copyResult = copyFileByChecksum(artifactoryURL, filePath, "${dstPath}/${relativePath}", credentialsId)
199
200 if (copyResult[0] != 200) {
201 errors.add("Failed to copy ${filePath}: HTTP ${copyResult[0]} - ${copyResult[1]}")
202 respCode = copyResult[0]
203 }
204 }
205
206 // Delete source directory if move (not copy)
207 if (!copy && respCode == 200) {
208 def deleteResult = deleteItem(artifactoryURL, sourcePath)
209 if (deleteResult[0] != 200) {
210 errors.add("Failed to delete source directory: HTTP ${deleteResult[0]}")
211 respCode = deleteResult[0]
212 }
213 }
214
215 respText = errors ? errors.join('; ') : "Successfully ${copy ? 'copied' : 'moved'} directory from ${sourcePath} to ${dstPath}"
216 } else {
217 // Handle single file using checksum deploy
218 def copyResult = copyFileByChecksum(artifactoryURL, sourcePath, dstPath, credentialsId)
219 respCode = copyResult[0]
220 respText = copyResult[1]
221 // Delete source file if move (not copy)
222 if (!copy && respCode == 200) {
223 def deleteResult = deleteItem(artifactoryURL, sourcePath)
224 if (deleteResult[0] != 200) {
225 respCode = deleteResult[0]
226 respText = "Copied but failed to delete source: ${deleteResult[1]}"
227 } else {
228 respText = respText ?: "Successfully ${copy ? 'copied' : 'moved'} file from ${sourcePath} to ${dstPath}"
229 }
230 } else if (respCode == 200) {
231 respText = respText ?: "Successfully ${copy ? 'copied' : 'moved'} file from ${sourcePath} to ${dstPath}"
232 }
233 }
234 //If successful, rewrite the return code to the expected one
235 if ( respCode ==~ /^2\d{2}$/ ) {
236 respCode = 200
237 }
238 } catch (Exception e) {
239 respCode = 500
240 respText = "Error during ${copy ? 'copy' : 'move'} operation: ${e.getMessage()}"
241 }
242 }
243
244 return [respCode, respText]
245}
246
247/**
248 * Copy a file using checksum-based deploy API (no file download required)
249 * Uses JFrog REST API: PUT /artifactory/api/checksum/deploy/{repoKey}/{filePath}
250 *
251 * @param artifactoryURL String, an URL to Artifactory
252 * @param sourcePath String, a source path to the artifact including repository name
253 * @param dstPath String, a destination path to the artifact including repository name
254 * @return Array with [responseCode, responseText]
255 */
256def copyFileByChecksum(String artifactoryURL, String sourcePath, String dstPath, String credentialsId = 'artifactory') {
257 def respCode = 200
258 def respText = ''
259
260 withCredentials([
261 [$class : 'UsernamePasswordMultiBinding',
262 credentialsId : credentialsId,
263 passwordVariable: 'ARTIFACTORY_PASSWORD',
264 usernameVariable: 'ARTIFACTORY_LOGIN']
265 ]) {
266 // Get checksums from source file
267 def storageUrl = "${artifactoryURL}/api/storage/${sourcePath}"
268 def storageResult = sh(script: """
269 set +e
270 response=\$(curl -s -w "\\n%{http_code}" -X GET -u \${ARTIFACTORY_LOGIN}:\${ARTIFACTORY_PASSWORD} '${storageUrl}' 2>&1)
271 echo "\$response"
272 """, returnStdout: true).trim()
273
274 def storageLines = storageResult.split('\n')
275 def storageCode = storageLines.length > 0 && storageLines[-1] ==~ /^\d{3}$/ ? storageLines[-1].toInteger() : 404
276 def storageBody = storageLines.length > 1 ? storageLines[0..-2].join('\n') : ''
277
278 if (storageCode != 200) {
279 return [storageCode, storageBody ?: "Source file not found: ${sourcePath}"]
280 }
281
282 def storageInfo = new groovy.json.JsonSlurperClassic().parseText(storageBody)
283 def checksums = storageInfo.get('checksums', [:])
284 def md5 = checksums.get('md5', '')
285 def sha1 = checksums.get('sha1', '')
286 def sha256 = checksums.get('sha256', '')
287
288 if (!md5 && !sha1) {
289 return [500, "Source file has no checksums available: ${sourcePath}"]
290 }
291
292 // Use checksum deploy API to copy file without downloading
293 def deployUrl = "${artifactoryURL}/${dstPath}"
294 // Build curl command with headers
295 def curlHeaders = "-H \"X-Checksum-Deploy: true\""
296 if (sha1) {
297 curlHeaders += " -H \"X-Checksum-Sha1: ${sha1}\""
298 }
299 if (sha256) {
300 curlHeaders += " -H \"X-Checksum-Sha256: ${sha256}\""
301 }
302 if (md5) {
303 curlHeaders += " -H \"X-Checksum-Md5: ${md5}\""
304 }
305
306 def deployResult = sh(script: """
307 set +e
308 response=\$(curl -s -w "\\n%{http_code}" -X PUT -u \${ARTIFACTORY_LOGIN}:\${ARTIFACTORY_PASSWORD} ${curlHeaders} '${deployUrl}' 2>&1)
309 echo "\$response"
310 """, returnStdout: true).trim()
311
312 def deployLines = deployResult.split('\n')
313 respCode = deployLines.length > 0 && deployLines[-1] ==~ /^\d{3}$/ ? deployLines[-1].toInteger() : 500
314 respText = deployLines.length > 1 ? deployLines[0..-2].join('\n') : ''
315
316 if (respCode != 200 && !respText) {
317 respText = "Failed to deploy by checksum: HTTP ${respCode}"
318 }
319 }
320
321 return [respCode, respText]
322}
323
324/**
325 * Recursively get all files in a directory
326 *
327 * @param artifactoryURL String, an URL to Artifactory
328 * @param dirPath String, a directory path including repository name
329 * @return List of file paths
330 */
331def getDirectoryFiles(String artifactoryURL, String dirPath, String credentialsId = 'artifactory') {
332 def files = []
333 def storageUrl = "${artifactoryURL}/api/storage/${dirPath}"
334
335 withCredentials([
336 [$class : 'UsernamePasswordMultiBinding',
337 credentialsId : credentialsId,
338 passwordVariable: 'ARTIFACTORY_PASSWORD',
339 usernameVariable: 'ARTIFACTORY_LOGIN']
340 ]) {
341 def result = sh(script: "bash -c \"curl -X GET -u \${ARTIFACTORY_LOGIN}:\${ARTIFACTORY_PASSWORD} '${storageUrl}'\"",
342 returnStdout: true).trim()
343
344 def storageInfo = new groovy.json.JsonSlurperClassic().parseText(result)
345 def children = storageInfo.get('children', [])
346
347 children.each { child ->
348 def childPath = "${dirPath}/${child.uri.replaceAll('^/', '')}"
349 if (child.folder) {
350 files.addAll(getDirectoryFiles(artifactoryURL, childPath))
351 } else {
352 files.add(childPath)
353 }
354 }
355 }
356 return files
Sergey Kolekonovce616712019-09-10 16:09:23 +0400357}
358
359/**
360 * Recursively delete the specified artifact or a folder
361 *
362 * @param artifactoryURL String, an URL to Artifactory
363 * @param itemPath String, a source path to the item including repository name
364 */
365def deleteItem (String artifactoryURL, String itemPath) {
366 def url = "${artifactoryURL}/${itemPath}"
Alexandr Lovtsov89d24a52021-01-28 17:41:14 +0300367 def http = new com.mirantis.mk.Http()
368 return http.doDelete(url, 'artifactory')
Sergey Kolekonovce616712019-09-10 16:09:23 +0400369}
370
371/**
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300372 * Get properties for specified artifact in Artifactory
373 * Returns LinkedHashMap of properties
374 *
375 * @param artifactUrl String, an URL to artifact in Artifactory repo
376 */
377def getPropertiesForArtifact(String artifactUrl) {
378 def url = "${artifactUrl}?properties"
379 def result
380 withCredentials([
381 [$class : 'UsernamePasswordMultiBinding',
382 credentialsId : 'artifactory',
383 passwordVariable: 'ARTIFACTORY_PASSWORD',
384 usernameVariable: 'ARTIFACTORY_LOGIN']
385 ]) {
386 result = sh(script: "bash -c \"curl -X GET -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} \'${url}\'\"",
387 returnStdout: true).trim()
388 }
389 def properties = new groovy.json.JsonSlurperClassic().parseText(result)
390 return properties.get("properties")
391}
392
393/**
vnaumov5b2dccf2019-10-10 22:12:15 +0200394 * Get checksums of artifact
395 *
396 * @param artifactoryUrl String, an URL ofArtifactory repo
397 * @param repoName Artifact repository name
398 * @param artifactName Artifactory object name
399 * @param checksumType Type of checksum (default md5)
400 */
401
402def getArtifactChecksum(artifactoryUrl, repoName, artifactName, checksumType = 'md5'){
403 def url = "${artifactoryUrl}/api/storage/${repoName}/${artifactName}"
404 withCredentials([
405 [$class : 'UsernamePasswordMultiBinding',
406 credentialsId : 'artifactory',
407 passwordVariable: 'ARTIFACTORY_PASSWORD',
408 usernameVariable: 'ARTIFACTORY_LOGIN']
409 ]) {
410 def result = sh(script: "bash -c \"curl -X GET -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} \'${url}\'\"",
411 returnStdout: true).trim()
412 }
413
414 def properties = new groovy.json.JsonSlurperClassic().parseText(result)
415 return properties['checksums'][checksumType]
416}
417
418/**
Denis Egorenkoedd21dc2018-11-23 17:38:17 +0400419 * Check if image with tag exist by provided path
420 * Returns true or false
421 *
422 * @param artifactoryURL String, an URL to Artifactory
423 * @param imageRepo String, path to image to check, includes repo path and image name
424 * @param tag String, tag to check
425 * @param artifactoryCreds String, artifactory creds to use. Optional, default is 'artifactory'
426 */
427def imageExists(String artifactoryURL, String imageRepo, String tag, String artifactoryCreds = 'artifactory') {
Sergey Otpuschennikov406778f2019-10-10 14:49:40 +0400428 def url = artifactoryURL + '/v2/' + imageRepo + '/manifests/' + tag
Denis Egorenkoedd21dc2018-11-23 17:38:17 +0400429 def result
430 withCredentials([
431 [$class : 'UsernamePasswordMultiBinding',
432 credentialsId : artifactoryCreds,
433 passwordVariable: 'ARTIFACTORY_PASSWORD',
434 usernameVariable: 'ARTIFACTORY_LOGIN']
435 ]) {
436 result = sh(script: "bash -c \"curl -X GET -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} \'${url}\'\"",
437 returnStdout: true).trim()
438 }
439 def properties = new groovy.json.JsonSlurperClassic().parseText(result)
440 return properties.get("errors") ? false : true
441}
442
443/**
Denis Egorenko7c0abfe2017-02-14 15:42:02 +0400444 * Find docker images by tag
445 * Returns Array of image' hashes with names as full path in @repo
446 *
447 * Example:
448 *
449 * [ {
450 * "path" : "mirantis/ccp/ci-cd/gerrit-manage/test"
451 * },
452 * {
453 * "path" : "mirantis/ccp/ci-cd/gerrit/test"
454 * }
455 * ]
456 *
457 * @param artifactoryURL String, an URL to Artifactory
458 * @param repo String, a name of repo where should be executed search
459 * @param tag String, tag of searched image
460 */
461def getImagesByTag(String artifactoryURL, String repo, String tag) {
462 def url = "${artifactoryURL}/api/search/aql"
463 def result
464 writeFile file: "query",
465 text: """\
466 items.find(
467 {
468 \"repo\": \"${repo}\",
469 \"@docker.manifest\": { \"\$match\" : \"${tag}*\" }
470 }
471 ).
472 include(\"path\")
473 """.stripIndent()
474 withCredentials([
475 [$class: 'UsernamePasswordMultiBinding',
476 credentialsId: 'artifactory',
477 passwordVariable: 'ARTIFACTORY_PASSWORD',
478 usernameVariable: 'ARTIFACTORY_LOGIN']
479 ]) {
480 result = sh(script: "bash -c \"curl -X POST -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} -d @query \'${url}\'\"",
481 returnStdout: true).trim()
482 }
483 def images = new groovy.json.JsonSlurperClassic().parseText(result)
484 return images.get("results")
485}
486
487/**
Alexandr Lovtsova0295432021-01-28 17:20:32 +0300488 * Convert Mirantis docker image url/path to Mirantis artifactory path ready for use in API calls
489 *
490 * For example:
491 * 'docker-dev-kaas-local.docker.mirantis.net/mirantis/kaas/si-test:master' -> 'docker-dev-kaas-local/mirantis/kaas/si-test/master'
492 *
493 */
494def dockerImageToArtifactoryPath(String image) {
495 List imageParts = image.tokenize('/')
496 String repoName = imageParts[0].tokenize('.')[0]
497 String namespace = imageParts[1..-2].join('/')
498 String imageName = imageParts[-1].tokenize(':')[0]
499 String imageTag = imageParts[-1].tokenize(':')[1]
500
501 return [repoName, namespace, imageName, imageTag].join('/')
502}
503
504/**
Alexandr Lovtsov8fb5d7c2021-01-28 17:42:24 +0300505 * Copy docker image from one url to another
506 *
507 * @param srcImage String, Mirantis URL/path for docker image to copy from
508 * @param dstImage String, Mirantis URL/path for docker image to copy to
509 */
510def copyDockerImage(String srcImage, String dstImage) {
511 def artifactoryServer = Artifactory.server(env.ARTIFACTORY_SERVER ?: 'mcp-ci')
512 String srcPath = dockerImageToArtifactoryPath(srcImage)
513 String dstPath = dockerImageToArtifactoryPath(dstImage)
514
515 return moveItem(artifactoryServer.getUrl(), srcPath, dstPath, true)
516}
517
518/**
519 * Delete docker image on Mirantis's artifactory
520 *
521 * @param image String, Mirantis URL/path for docker image to delete
522 */
523def deleteDockerImage(String image) {
524 def artifactoryServer = Artifactory.server(env.ARTIFACTORY_SERVER ?: 'mcp-ci')
525
526 return deleteItem(artifactoryServer.getUrl() + '/artifactory', dockerImageToArtifactoryPath(image))
527}
528
529/**
Alexandr Lovtsovd3023d02021-05-12 15:54:44 +0300530 * Upload list of docker images to Artifactory
531 *
532 * @param server ArtifactoryServer, the instance of Artifactory server
533 * @param registry String, the name of Docker registry
534 * @param images List[Map], list of maps where each map consist of following fields:
535 * {
536 * 'repository': String '...', // (mandatory) The name of Artifactory Docker repository
537 * 'name': String '...', // (mandatory) docker image name
538 * 'tag': String '...', // (mandatory) docker image tag/version
539 * 'buildInfo': BuildInfo '...', // (optional) the instance of a build-info object which
540 * can be published, if it's not null (default),
541 * then we publish BuildInfo,
542 * 'properties': LinkedHashMap '...', // (optional) Map of artifactory properties to set for image,
543 * if not provided, then some common properties will be set
544 * }
545 *
546 */
547def uploadImagesToArtifactory(ArtifactoryServer server, String registry, List images) {
548 // Check that every provided image's specs contain mandatory fields (name, tag, repository)
549 images.each {
550 if (!(it.name && it.tag && it.repository)) {
551 error("Incorrect image upload spec: ${it}")
552 }
553 }
554
555 // TODO Switch to Artifactoy image' pushing mechanism once we will
556 // prepare automatical way for enabling artifactory build-proxy
557 //def artDocker
558 withCredentials([[
559 $class: 'UsernamePasswordMultiBinding',
560 credentialsId: env.ARTIFACTORY_CREDENTIALS_ID ?: 'artifactory',
561 passwordVariable: 'ARTIFACTORY_PASSWORD',
562 usernameVariable: 'ARTIFACTORY_LOGIN',
563 ]]) {
564 sh ("docker login -u ${ARTIFACTORY_LOGIN} -p ${ARTIFACTORY_PASSWORD} ${registry}")
565 //artDocker = Artifactory.docker("${env.ARTIFACTORY_LOGIN}", "${env.ARTIFACTORY_PASSWORD}")
566 }
567
568 images.each {
569 String image = it.name // mandatory
570 String version = it.tag // mandatory
571 String repository = it.repository // mandatory
572
573 sh ("docker push ${registry}/${image}:${version}")
574 //artDocker.push("${registry}/${image}:${version}", repository)
575 def image_url = server.getUrl() + "/api/storage/${repository}/${image}/${version}"
576
577 LinkedHashMap properties = it.get('properties')
578 if ( ! properties ) {
579 properties = [
580 'com.mirantis.buildName':"${env.JOB_NAME}",
581 'com.mirantis.buildNumber': "${env.BUILD_NUMBER}",
582 'com.mirantis.gerritProject': "${env.GERRIT_PROJECT}",
583 'com.mirantis.gerritChangeNumber': "${env.GERRIT_CHANGE_NUMBER}",
584 'com.mirantis.gerritPatchsetNumber': "${env.GERRIT_PATCHSET_NUMBER}",
585 'com.mirantis.gerritChangeId': "${env.GERRIT_CHANGE_ID}",
586 'com.mirantis.gerritPatchsetRevision': "${env.GERRIT_PATCHSET_REVISION}",
587 'com.mirantis.targetImg': "${image}",
588 'com.mirantis.targetTag': "${version}"
589 ]
590 }
591
592 setProperties(image_url, properties)
593
594 BuildInfo buildInfo = it.get('buildInfo')
595 if ( buildInfo != null ) {
596 buildInfo.env.capture = true
597 buildInfo.env.filter.addInclude("*")
598 buildInfo.env.filter.addExclude("*PASSWORD*")
599 buildInfo.env.filter.addExclude("*password*")
600 buildInfo.env.collect()
601 server.publishBuildInfo(buildInfo)
602 }
603 }
604}
605/**
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300606 * Upload docker image to Artifactory
607 *
Sergey Kulanov8cd6d222016-11-17 13:42:47 +0200608 * @param server ArtifactoryServer, the instance of Artifactory server
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300609 * @param registry String, the name of Docker registry
610 * @param image String, Docker image name
611 * @param version String, Docker image version
612 * @param repository String, The name of Artifactory Docker repository
Sergey Kulanov8cd6d222016-11-17 13:42:47 +0200613 * @param buildInfo BuildInfo, the instance of a build-info object which can be published,
614 * if defined, then we publish BuildInfo
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300615 */
Sergey Kulanov8cd6d222016-11-17 13:42:47 +0200616def uploadImageToArtifactory (ArtifactoryServer server, String registry, String image,
617 String version, String repository,
Dmitry Burmistrov6ee39522017-05-22 12:46:25 +0400618 BuildInfo buildInfo = null,
619 LinkedHashMap properties = null) {
Alexandr Lovtsovd3023d02021-05-12 15:54:44 +0300620 Map images = [
621 'repository': repository,
622 'name': image,
623 'tag': version,
624 'buildInfo': buildInfo,
625 'properties': properties,
626 ]
627 uploadImagesToArtifactory(server, registry, [images])
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300628}
629
630/**
631 * Upload binaries to Artifactory
632 *
633 * @param server ArtifactoryServer, the instance of Artifactory server
634 * @param buildInfo BuildInfo, the instance of a build-info object which can be published
635 * @param uploadSpec String, a spec which is a JSON file that specifies which files should be
636 * uploaded or downloaded and the target path
637 * @param publishInfo Boolean, whether publish a build-info object to Artifactory
638 */
Sergey Kulanov91d8def2016-11-15 13:53:17 +0200639def uploadBinariesToArtifactory (ArtifactoryServer server, BuildInfo buildInfo, String uploadSpec,
640 Boolean publishInfo = false) {
Jakub Josefbefcf6c2017-11-14 18:03:10 +0100641 server.upload(uploadSpec, buildInfo)
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300642
643 if ( publishInfo ) {
644 buildInfo.env.capture = true
645 buildInfo.env.filter.addInclude("*")
646 buildInfo.env.filter.addExclude("*PASSWORD*")
647 buildInfo.env.filter.addExclude("*password*")
648 buildInfo.env.collect()
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300649 server.publishBuildInfo(buildInfo)
650 }
651}
652
653/**
654 * Promote Docker image artifact to release repo
655 *
656 * @param artifactoryURL String, an URL to Artifactory
657 * @param artifactoryDevRepo String, the source dev repository name
658 * @param artifactoryProdRepo String, the target repository for the move or copy
659 * @param dockerRepo String, the docker repository name to promote
660 * @param artifactTag String, an image tag name to promote
661 * @param targetTag String, target tag to assign the image after promotion
662 * @param copy Boolean, an optional value to set whether to copy instead of move
663 * Default: false
664 */
665def promoteDockerArtifact(String artifactoryURL, String artifactoryDevRepo,
666 String artifactoryProdRepo, String dockerRepo,
667 String artifactTag, String targetTag, Boolean copy = false) {
668 def url = "${artifactoryURL}/api/docker/${artifactoryDevRepo}/v2/promote"
Dmitry Burmistrov5deaa7d2017-05-30 17:12:54 +0400669 String queryFile = UUID.randomUUID().toString()
Dmitry Burmistrov97beb9b2017-05-29 17:21:34 +0400670 writeFile file: queryFile,
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300671 text: """{
672 \"targetRepo\": \"${artifactoryProdRepo}\",
673 \"dockerRepository\": \"${dockerRepo}\",
674 \"tag\": \"${artifactTag}\",
675 \"targetTag\" : \"${targetTag}\",
676 \"copy\": \"${copy}\"
677 }""".stripIndent()
Dmitry Burmistrov97beb9b2017-05-29 17:21:34 +0400678 sh "cat ${queryFile}"
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300679 withCredentials([
680 [$class : 'UsernamePasswordMultiBinding',
681 credentialsId : 'artifactory',
682 passwordVariable: 'ARTIFACTORY_PASSWORD',
683 usernameVariable: 'ARTIFACTORY_LOGIN']
684 ]) {
Sergey Reshetnyakf0775fb2018-06-28 14:54:01 +0400685 sh "bash -c \"curl --fail -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} -H \"Content-Type:application/json\" -X POST -d @${queryFile} ${url}\""
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300686 }
Dmitry Burmistrov97beb9b2017-05-29 17:21:34 +0400687 sh "rm -v ${queryFile}"
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300688}
Denis Egorenko60f47c12019-03-11 20:54:13 +0400689
690/**
691 * Save job artifacts to Artifactory server if available.
692 * Returns link to Artifactory repo, where saved job artifacts.
693 *
694 * @param config LinkedHashMap which contains next parameters:
695 * @param artifactory String, Artifactory server id
696 * @param artifactoryRepo String, repo to save job artifacts
697 * @param buildProps ArrayList, additional props for saved artifacts. Optional, default: []
698 * @param artifactory_not_found_fail Boolean, whether to fail if provided artifactory
699 * id is not found or just print warning message. Optional, default: false
700 */
701def uploadJobArtifactsToArtifactory(LinkedHashMap config) {
702 def common = new com.mirantis.mk.Common()
703 def artifactsDescription = ''
704 def artifactoryServer
Dmitry Tyzhnenko9ade0722020-03-31 13:17:54 +0300705
706 if (!config.containsKey('deleteArtifacts')) {
707 config.deleteArtifacts = true // default behavior before add the flag
708 }
709
Denis Egorenko60f47c12019-03-11 20:54:13 +0400710 try {
711 artifactoryServer = Artifactory.server(config.get('artifactory'))
712 } catch (Exception e) {
713 if (config.get('artifactory_not_found_fail', false)) {
714 throw e
715 } else {
716 common.warningMsg(e)
717 return "Artifactory server is not found. Can't save artifacts in Artifactory."
718 }
719 }
Dmitry Tyzhnenko812673a2020-03-26 21:59:14 +0200720 def artifactDir = config.get('artifactDir') ?: 'cur_build_artifacts'
Denis Egorenko60f47c12019-03-11 20:54:13 +0400721 def user = ''
722 wrap([$class: 'BuildUser']) {
723 user = env.BUILD_USER_ID
724 }
725 dir(artifactDir) {
726 try {
Denis Egorenko5fc40f82019-03-13 18:35:51 +0400727 unarchive(mapping: ['**/*' : '.'])
Denis Egorenko60f47c12019-03-11 20:54:13 +0400728 // Mandatory and additional properties
729 def properties = getBinaryBuildProperties(config.get('buildProps', []) << "buildUser=${user}")
Dmitry Tyzhnenko812673a2020-03-26 21:59:14 +0200730 def pattern = config.get('artifactPattern') ?: '*'
Denis Egorenko60f47c12019-03-11 20:54:13 +0400731
732 // Build Artifactory spec object
733 def uploadSpec = """{
734 "files":
735 [
736 {
Dmitry Tyzhnenko812673a2020-03-26 21:59:14 +0200737 "pattern": "${pattern}",
Denis Egorenko60f47c12019-03-11 20:54:13 +0400738 "target": "${config.get('artifactoryRepo')}/",
Denis Egorenko850f56a2019-03-13 20:44:43 +0400739 "flat": false,
Denis Egorenko60f47c12019-03-11 20:54:13 +0400740 "props": "${properties}"
741 }
742 ]
743 }"""
744
745 artifactoryServer.upload(uploadSpec, newBuildInfo())
746 def linkUrl = "${artifactoryServer.getUrl()}/artifactory/${config.get('artifactoryRepo')}"
747 artifactsDescription = "Job artifacts uploaded to Artifactory: <a href=\"${linkUrl}\">${linkUrl}</a>"
748 } catch (Exception e) {
749 if (e =~ /no artifacts/) {
750 artifactsDescription = 'Build has no artifacts saved.'
751 } else {
752 throw e
753 }
754 } finally {
Dmitry Tyzhnenko9ade0722020-03-31 13:17:54 +0300755 if (config.deleteArtifacts) {
756 deleteDir()
757 }
Denis Egorenko60f47c12019-03-11 20:54:13 +0400758 }
759 }
760 return artifactsDescription
761}
Dmitry Tyzhnenko39cf09c2020-05-05 20:08:52 +0300762
763/**
764 * Save custom artifacts to Artifactory server if available.
765 * Returns link to Artifactory repo, where saved artifacts.
766 *
767 * @param config LinkedHashMap which contains next parameters:
768 * @param artifactory String, Artifactory server id
769 * @param artifactoryRepo String, repo to save job artifacts
770 * @param buildProps ArrayList, additional props for saved artifacts. Optional, default: []
771 * @param artifactory_not_found_fail Boolean, whether to fail if provided artifactory
772 * id is not found or just print warning message. Optional, default: false
773 */
774def uploadArtifactsToArtifactory(LinkedHashMap config) {
775 def common = new com.mirantis.mk.Common()
776 def artifactsDescription = ''
777 def artifactoryServer
778
779 try {
780 artifactoryServer = Artifactory.server(config.get('artifactory'))
781 } catch (Exception e) {
782 if (config.get('artifactory_not_found_fail', false)) {
783 throw e
784 } else {
785 common.warningMsg(e)
786 return "Artifactory server is not found. Can't save artifacts in Artifactory."
787 }
788 }
789 def user = ''
790 wrap([$class: 'BuildUser']) {
791 user = env.BUILD_USER_ID
792 }
793 try {
794 // Mandatory and additional properties
795 def properties = getBinaryBuildProperties(config.get('buildProps', []) << "buildUser=${user}")
796 def pattern = config.get('artifactPattern') ?: '*'
797
798 // Build Artifactory spec object
799 def uploadSpec = """{
800 "files":
801 [
802 {
803 "pattern": "${pattern}",
804 "target": "${config.get('artifactoryRepo')}/",
805 "flat": false,
806 "props": "${properties}"
807 }
808 ]
809 }"""
810
811 artifactoryServer.upload(uploadSpec, newBuildInfo())
Alexandr Lovtsov9293d992021-01-19 19:55:41 +0300812 def linkUrl = "${artifactoryServer.getUrl()}/${config.get('artifactoryRepo')}"
Dmitry Tyzhnenko39cf09c2020-05-05 20:08:52 +0300813 artifactsDescription = "Job artifacts uploaded to Artifactory: <a href=\"${linkUrl}\">${linkUrl}</a>"
814 } catch (Exception e) {
815 if (e =~ /no artifacts/) {
816 artifactsDescription = 'Build has no artifacts saved.'
817 } else {
818 throw e
819 }
820 }
821 return artifactsDescription
822}
Andrii Baraniukf9992ca2023-05-04 11:43:44 +0300823
824/**
825 * Get artifactory server object
826 *
827 * @param serverName Artifactory server name
828 */
829def getArtifactoryServer(serverName = ''){
830 if (!serverName) {
831 error ("Artifactory serverName must be specified")
832 }
833 return Artifactory.server(serverName)
834}