| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 1 | package com.mirantis.mcp |
| 2 | |
| Sergey Kolekonov | 74a6b6e | 2019-06-28 11:45:47 +0400 | [diff] [blame] | 3 | import org.jfrog.hudson.pipeline.common.types.ArtifactoryServer |
| 4 | import org.jfrog.hudson.pipeline.common.types.buildInfo.BuildInfo |
| Sergey Kulanov | 91d8def | 2016-11-15 13:53:17 +0200 | [diff] [blame] | 5 | |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 6 | /** |
| 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 | * */ |
| 13 | def getBinaryBuildProperties(ArrayList customProperties) { |
| 14 | def namespace = "com.mirantis." |
| 15 | def properties = [ |
| Sergey Kulanov | c70f1c2 | 2016-11-16 13:05:20 +0200 | [diff] [blame] | 16 | "buildName=${env.JOB_NAME}", |
| 17 | "buildNumber=${env.BUILD_NUMBER}", |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 18 | "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 Mashchenko | 1d225c2 | 2018-06-19 13:52:17 +0300 | [diff] [blame] | 35 | * Get URL to artifact(s) by properties |
| 36 | * Returns String(s) with URL to found artifact or null if nothing |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 37 | * |
| 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 Mashchenko | 1d225c2 | 2018-06-19 13:52:17 +0300 | [diff] [blame] | 41 | * @param onlyLastItem Boolean, return only last URL if true(by default), |
| 42 | * else return list of all found artifact URLS |
| Sergey Kolekonov | 54c4484 | 2019-06-17 19:25:52 +0400 | [diff] [blame] | 43 | * @param repos ArrayList, a list of repositories to search in |
| Kirill Mashchenko | 1d225c2 | 2018-06-19 13:52:17 +0300 | [diff] [blame] | 44 | * |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 45 | */ |
| Sergey Kolekonov | 54c4484 | 2019-06-17 19:25:52 +0400 | [diff] [blame] | 46 | def uriByProperties(String artifactoryURL, LinkedHashMap properties, Boolean onlyLastItem=true, ArrayList repos=[]) { |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 47 | def key, value |
| 48 | def properties_str = '' |
| 49 | for (int i = 0; i < properties.size(); i++) { |
| 50 | // avoid serialization errors |
| Kirill Mashchenko | 56c8ff3 | 2018-06-28 03:01:34 +0300 | [diff] [blame] | 51 | key = properties.entrySet().toArray()[i].key.trim() |
| 52 | value = properties.entrySet().toArray()[i].value.trim() |
| 53 | properties_str += /${key}=${value}&/ |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 54 | } |
| Sergey Kolekonov | 54c4484 | 2019-06-17 19:25:52 +0400 | [diff] [blame] | 55 | 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 Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 62 | |
| Kirill Mashchenko | 56c8ff3 | 2018-06-28 03:01:34 +0300 | [diff] [blame] | 63 | def result = sh(script: /curl -X GET '${search_url}'/, |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 64 | returnStdout: true).trim() |
| 65 | def content = new groovy.json.JsonSlurperClassic().parseText(result) |
| 66 | def uri = content.get("results") |
| 67 | if (uri) { |
| Kirill Mashchenko | 1d225c2 | 2018-06-19 13:52:17 +0300 | [diff] [blame] | 68 | 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 Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 77 | } else { |
| 78 | return null |
| 79 | } |
| 80 | } |
| 81 | |
| Kirill Mashchenko | 1d225c2 | 2018-06-19 13:52:17 +0300 | [diff] [blame] | 82 | |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 83 | /** |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 84 | * 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 | */ |
| 92 | def 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 Evseev | bd40ef9 | 2017-10-18 12:24:45 +0300 | [diff] [blame] | 100 | properties_str += properties.collect({"${it.key}=${it.value}"}).join(';') |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 101 | 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 Kolekonov | 76c17f5 | 2019-09-09 16:55:01 +0400 | [diff] [blame] | 113 | * 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 | */ |
| 119 | def 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 Kolekonov | ce61671 | 2019-09-10 16:09:23 +0400 | [diff] [blame] | 132 | * Move/copy an artifact or a folder to the specified destination |
| Sergei Otpushchennikov | e204aac | 2025-12-02 09:36:04 +0300 | [diff] [blame^] | 133 | * |
| 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 | */ |
| 140 | def 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 Vialov | d36c1c0 | 2025-11-21 12:49:11 +0600 | [diff] [blame] | 148 | * Uses curl to download/upload/delete files since /api/copy and /api/move are not supported |
| Sergey Kolekonov | ce61671 | 2019-09-10 16:09:23 +0400 | [diff] [blame] | 149 | * |
| 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 Otpushchennikov | e204aac | 2025-12-02 09:36:04 +0300 | [diff] [blame^] | 156 | def moveItemNew (String artifactoryURL, String sourcePath, String dstPath, boolean copy = false, boolean dryRun = false, String credentialsId = 'artifactory') { |
| Roman Vialov | d36c1c0 | 2025-11-21 12:49:11 +0600 | [diff] [blame] | 157 | 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 | */ |
| 256 | def 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 | */ |
| 331 | def 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 Kolekonov | ce61671 | 2019-09-10 16:09:23 +0400 | [diff] [blame] | 357 | } |
| 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 | */ |
| 365 | def deleteItem (String artifactoryURL, String itemPath) { |
| 366 | def url = "${artifactoryURL}/${itemPath}" |
| Alexandr Lovtsov | 89d24a5 | 2021-01-28 17:41:14 +0300 | [diff] [blame] | 367 | def http = new com.mirantis.mk.Http() |
| 368 | return http.doDelete(url, 'artifactory') |
| Sergey Kolekonov | ce61671 | 2019-09-10 16:09:23 +0400 | [diff] [blame] | 369 | } |
| 370 | |
| 371 | /** |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 372 | * 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 | */ |
| 377 | def 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 | /** |
| vnaumov | 5b2dccf | 2019-10-10 22:12:15 +0200 | [diff] [blame] | 394 | * 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 | |
| 402 | def 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 Egorenko | edd21dc | 2018-11-23 17:38:17 +0400 | [diff] [blame] | 419 | * 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 | */ |
| 427 | def imageExists(String artifactoryURL, String imageRepo, String tag, String artifactoryCreds = 'artifactory') { |
| Sergey Otpuschennikov | 406778f | 2019-10-10 14:49:40 +0400 | [diff] [blame] | 428 | def url = artifactoryURL + '/v2/' + imageRepo + '/manifests/' + tag |
| Denis Egorenko | edd21dc | 2018-11-23 17:38:17 +0400 | [diff] [blame] | 429 | 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 Egorenko | 7c0abfe | 2017-02-14 15:42:02 +0400 | [diff] [blame] | 444 | * 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 | */ |
| 461 | def 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 Lovtsov | a029543 | 2021-01-28 17:20:32 +0300 | [diff] [blame] | 488 | * 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 | */ |
| 494 | def 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 Lovtsov | 8fb5d7c | 2021-01-28 17:42:24 +0300 | [diff] [blame] | 505 | * 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 | */ |
| 510 | def 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 | */ |
| 523 | def 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 Lovtsov | d3023d0 | 2021-05-12 15:54:44 +0300 | [diff] [blame] | 530 | * 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 | */ |
| 547 | def 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 Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 606 | * Upload docker image to Artifactory |
| 607 | * |
| Sergey Kulanov | 8cd6d22 | 2016-11-17 13:42:47 +0200 | [diff] [blame] | 608 | * @param server ArtifactoryServer, the instance of Artifactory server |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 609 | * @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 Kulanov | 8cd6d22 | 2016-11-17 13:42:47 +0200 | [diff] [blame] | 613 | * @param buildInfo BuildInfo, the instance of a build-info object which can be published, |
| 614 | * if defined, then we publish BuildInfo |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 615 | */ |
| Sergey Kulanov | 8cd6d22 | 2016-11-17 13:42:47 +0200 | [diff] [blame] | 616 | def uploadImageToArtifactory (ArtifactoryServer server, String registry, String image, |
| 617 | String version, String repository, |
| Dmitry Burmistrov | 6ee3952 | 2017-05-22 12:46:25 +0400 | [diff] [blame] | 618 | BuildInfo buildInfo = null, |
| 619 | LinkedHashMap properties = null) { |
| Alexandr Lovtsov | d3023d0 | 2021-05-12 15:54:44 +0300 | [diff] [blame] | 620 | Map images = [ |
| 621 | 'repository': repository, |
| 622 | 'name': image, |
| 623 | 'tag': version, |
| 624 | 'buildInfo': buildInfo, |
| 625 | 'properties': properties, |
| 626 | ] |
| 627 | uploadImagesToArtifactory(server, registry, [images]) |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 628 | } |
| 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 Kulanov | 91d8def | 2016-11-15 13:53:17 +0200 | [diff] [blame] | 639 | def uploadBinariesToArtifactory (ArtifactoryServer server, BuildInfo buildInfo, String uploadSpec, |
| 640 | Boolean publishInfo = false) { |
| Jakub Josef | befcf6c | 2017-11-14 18:03:10 +0100 | [diff] [blame] | 641 | server.upload(uploadSpec, buildInfo) |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 642 | |
| 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 Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 649 | 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 | */ |
| 665 | def 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 Burmistrov | 5deaa7d | 2017-05-30 17:12:54 +0400 | [diff] [blame] | 669 | String queryFile = UUID.randomUUID().toString() |
| Dmitry Burmistrov | 97beb9b | 2017-05-29 17:21:34 +0400 | [diff] [blame] | 670 | writeFile file: queryFile, |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 671 | text: """{ |
| 672 | \"targetRepo\": \"${artifactoryProdRepo}\", |
| 673 | \"dockerRepository\": \"${dockerRepo}\", |
| 674 | \"tag\": \"${artifactTag}\", |
| 675 | \"targetTag\" : \"${targetTag}\", |
| 676 | \"copy\": \"${copy}\" |
| 677 | }""".stripIndent() |
| Dmitry Burmistrov | 97beb9b | 2017-05-29 17:21:34 +0400 | [diff] [blame] | 678 | sh "cat ${queryFile}" |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 679 | withCredentials([ |
| 680 | [$class : 'UsernamePasswordMultiBinding', |
| 681 | credentialsId : 'artifactory', |
| 682 | passwordVariable: 'ARTIFACTORY_PASSWORD', |
| 683 | usernameVariable: 'ARTIFACTORY_LOGIN'] |
| 684 | ]) { |
| Sergey Reshetnyak | f0775fb | 2018-06-28 14:54:01 +0400 | [diff] [blame] | 685 | sh "bash -c \"curl --fail -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} -H \"Content-Type:application/json\" -X POST -d @${queryFile} ${url}\"" |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 686 | } |
| Dmitry Burmistrov | 97beb9b | 2017-05-29 17:21:34 +0400 | [diff] [blame] | 687 | sh "rm -v ${queryFile}" |
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 688 | } |
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 689 | |
| 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 | */ |
| 701 | def uploadJobArtifactsToArtifactory(LinkedHashMap config) { |
| 702 | def common = new com.mirantis.mk.Common() |
| 703 | def artifactsDescription = '' |
| 704 | def artifactoryServer |
| Dmitry Tyzhnenko | 9ade072 | 2020-03-31 13:17:54 +0300 | [diff] [blame] | 705 | |
| 706 | if (!config.containsKey('deleteArtifacts')) { |
| 707 | config.deleteArtifacts = true // default behavior before add the flag |
| 708 | } |
| 709 | |
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 710 | 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 Tyzhnenko | 812673a | 2020-03-26 21:59:14 +0200 | [diff] [blame] | 720 | def artifactDir = config.get('artifactDir') ?: 'cur_build_artifacts' |
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 721 | def user = '' |
| 722 | wrap([$class: 'BuildUser']) { |
| 723 | user = env.BUILD_USER_ID |
| 724 | } |
| 725 | dir(artifactDir) { |
| 726 | try { |
| Denis Egorenko | 5fc40f8 | 2019-03-13 18:35:51 +0400 | [diff] [blame] | 727 | unarchive(mapping: ['**/*' : '.']) |
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 728 | // Mandatory and additional properties |
| 729 | def properties = getBinaryBuildProperties(config.get('buildProps', []) << "buildUser=${user}") |
| Dmitry Tyzhnenko | 812673a | 2020-03-26 21:59:14 +0200 | [diff] [blame] | 730 | def pattern = config.get('artifactPattern') ?: '*' |
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 731 | |
| 732 | // Build Artifactory spec object |
| 733 | def uploadSpec = """{ |
| 734 | "files": |
| 735 | [ |
| 736 | { |
| Dmitry Tyzhnenko | 812673a | 2020-03-26 21:59:14 +0200 | [diff] [blame] | 737 | "pattern": "${pattern}", |
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 738 | "target": "${config.get('artifactoryRepo')}/", |
| Denis Egorenko | 850f56a | 2019-03-13 20:44:43 +0400 | [diff] [blame] | 739 | "flat": false, |
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 740 | "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 Tyzhnenko | 9ade072 | 2020-03-31 13:17:54 +0300 | [diff] [blame] | 755 | if (config.deleteArtifacts) { |
| 756 | deleteDir() |
| 757 | } |
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 758 | } |
| 759 | } |
| 760 | return artifactsDescription |
| 761 | } |
| Dmitry Tyzhnenko | 39cf09c | 2020-05-05 20:08:52 +0300 | [diff] [blame] | 762 | |
| 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 | */ |
| 774 | def 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 Lovtsov | 9293d99 | 2021-01-19 19:55:41 +0300 | [diff] [blame] | 812 | def linkUrl = "${artifactoryServer.getUrl()}/${config.get('artifactoryRepo')}" |
| Dmitry Tyzhnenko | 39cf09c | 2020-05-05 20:08:52 +0300 | [diff] [blame] | 813 | 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 Baraniuk | f9992ca | 2023-05-04 11:43:44 +0300 | [diff] [blame] | 823 | |
| 824 | /** |
| 825 | * Get artifactory server object |
| 826 | * |
| 827 | * @param serverName Artifactory server name |
| 828 | */ |
| 829 | def getArtifactoryServer(serverName = ''){ |
| 830 | if (!serverName) { |
| 831 | error ("Artifactory serverName must be specified") |
| 832 | } |
| 833 | return Artifactory.server(serverName) |
| 834 | } |