| 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 | 
|  | 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'}" | 
| Alexandr Lovtsov | 066f488 | 2021-01-18 17:29:26 +0300 | [diff] [blame] | 142 | def http = new com.mirantis.mk.Http() | 
|  | 143 | return http.doPost(url, 'artifactory') | 
| Sergey Kolekonov | ce61671 | 2019-09-10 16:09:23 +0400 | [diff] [blame] | 144 | } | 
|  | 145 |  | 
|  | 146 | /** | 
|  | 147 | * Recursively delete the specified artifact or a folder | 
|  | 148 | * | 
|  | 149 | * @param artifactoryURL String, an URL to Artifactory | 
|  | 150 | * @param itemPath String, a source path to the item including repository name | 
|  | 151 | */ | 
|  | 152 | def deleteItem (String artifactoryURL, String itemPath) { | 
|  | 153 | def url = "${artifactoryURL}/${itemPath}" | 
| Alexandr Lovtsov | 89d24a5 | 2021-01-28 17:41:14 +0300 | [diff] [blame] | 154 | def http = new com.mirantis.mk.Http() | 
|  | 155 | return http.doDelete(url, 'artifactory') | 
| Sergey Kolekonov | ce61671 | 2019-09-10 16:09:23 +0400 | [diff] [blame] | 156 | } | 
|  | 157 |  | 
|  | 158 | /** | 
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 159 | * Get properties for specified artifact in Artifactory | 
|  | 160 | * Returns LinkedHashMap of properties | 
|  | 161 | * | 
|  | 162 | * @param artifactUrl String, an URL to artifact in Artifactory repo | 
|  | 163 | */ | 
|  | 164 | def getPropertiesForArtifact(String artifactUrl) { | 
|  | 165 | def url = "${artifactUrl}?properties" | 
|  | 166 | def result | 
|  | 167 | withCredentials([ | 
|  | 168 | [$class          : 'UsernamePasswordMultiBinding', | 
|  | 169 | credentialsId   : 'artifactory', | 
|  | 170 | passwordVariable: 'ARTIFACTORY_PASSWORD', | 
|  | 171 | usernameVariable: 'ARTIFACTORY_LOGIN'] | 
|  | 172 | ]) { | 
|  | 173 | result = sh(script: "bash -c \"curl -X GET -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} \'${url}\'\"", | 
|  | 174 | returnStdout: true).trim() | 
|  | 175 | } | 
|  | 176 | def properties = new groovy.json.JsonSlurperClassic().parseText(result) | 
|  | 177 | return properties.get("properties") | 
|  | 178 | } | 
|  | 179 |  | 
|  | 180 | /** | 
| vnaumov | 5b2dccf | 2019-10-10 22:12:15 +0200 | [diff] [blame] | 181 | * Get checksums of artifact | 
|  | 182 | * | 
|  | 183 | * @param artifactoryUrl   String, an URL ofArtifactory repo | 
|  | 184 | * @param repoName         Artifact repository name | 
|  | 185 | * @param artifactName     Artifactory object name | 
|  | 186 | * @param checksumType     Type of checksum (default md5) | 
|  | 187 | */ | 
|  | 188 |  | 
|  | 189 | def getArtifactChecksum(artifactoryUrl, repoName, artifactName, checksumType = 'md5'){ | 
|  | 190 | def url = "${artifactoryUrl}/api/storage/${repoName}/${artifactName}" | 
|  | 191 | withCredentials([ | 
|  | 192 | [$class          : 'UsernamePasswordMultiBinding', | 
|  | 193 | credentialsId   : 'artifactory', | 
|  | 194 | passwordVariable: 'ARTIFACTORY_PASSWORD', | 
|  | 195 | usernameVariable: 'ARTIFACTORY_LOGIN'] | 
|  | 196 | ]) { | 
|  | 197 | def result = sh(script: "bash -c \"curl -X GET -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} \'${url}\'\"", | 
|  | 198 | returnStdout: true).trim() | 
|  | 199 | } | 
|  | 200 |  | 
|  | 201 | def properties = new groovy.json.JsonSlurperClassic().parseText(result) | 
|  | 202 | return properties['checksums'][checksumType] | 
|  | 203 | } | 
|  | 204 |  | 
|  | 205 | /** | 
| Denis Egorenko | edd21dc | 2018-11-23 17:38:17 +0400 | [diff] [blame] | 206 | * Check if image with tag exist by provided path | 
|  | 207 | * Returns true or false | 
|  | 208 | * | 
|  | 209 | * @param artifactoryURL String, an URL to Artifactory | 
|  | 210 | * @param imageRepo String, path to image to check, includes repo path and image name | 
|  | 211 | * @param tag String, tag to check | 
|  | 212 | * @param artifactoryCreds String, artifactory creds to use. Optional, default is 'artifactory' | 
|  | 213 | */ | 
|  | 214 | def imageExists(String artifactoryURL, String imageRepo, String tag, String artifactoryCreds = 'artifactory') { | 
| Sergey Otpuschennikov | 406778f | 2019-10-10 14:49:40 +0400 | [diff] [blame] | 215 | def url = artifactoryURL + '/v2/' + imageRepo + '/manifests/' + tag | 
| Denis Egorenko | edd21dc | 2018-11-23 17:38:17 +0400 | [diff] [blame] | 216 | def result | 
|  | 217 | withCredentials([ | 
|  | 218 | [$class          : 'UsernamePasswordMultiBinding', | 
|  | 219 | credentialsId   : artifactoryCreds, | 
|  | 220 | passwordVariable: 'ARTIFACTORY_PASSWORD', | 
|  | 221 | usernameVariable: 'ARTIFACTORY_LOGIN'] | 
|  | 222 | ]) { | 
|  | 223 | result = sh(script: "bash -c \"curl -X GET -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} \'${url}\'\"", | 
|  | 224 | returnStdout: true).trim() | 
|  | 225 | } | 
|  | 226 | def properties = new groovy.json.JsonSlurperClassic().parseText(result) | 
|  | 227 | return properties.get("errors") ? false : true | 
|  | 228 | } | 
|  | 229 |  | 
|  | 230 | /** | 
| Denis Egorenko | 7c0abfe | 2017-02-14 15:42:02 +0400 | [diff] [blame] | 231 | * Find docker images by tag | 
|  | 232 | * Returns Array of image' hashes with names as full path in @repo | 
|  | 233 | * | 
|  | 234 | * Example: | 
|  | 235 | * | 
|  | 236 | *   [ { | 
|  | 237 | *       "path" : "mirantis/ccp/ci-cd/gerrit-manage/test" | 
|  | 238 | *     }, | 
|  | 239 | *     { | 
|  | 240 | *       "path" : "mirantis/ccp/ci-cd/gerrit/test" | 
|  | 241 | *     } | 
|  | 242 | *   ] | 
|  | 243 | * | 
|  | 244 | * @param artifactoryURL String, an URL to Artifactory | 
|  | 245 | * @param repo String, a name of repo where should be executed search | 
|  | 246 | * @param tag String, tag of searched image | 
|  | 247 | */ | 
|  | 248 | def getImagesByTag(String artifactoryURL, String repo, String tag) { | 
|  | 249 | def url = "${artifactoryURL}/api/search/aql" | 
|  | 250 | def result | 
|  | 251 | writeFile file: "query", | 
|  | 252 | text: """\ | 
|  | 253 | items.find( | 
|  | 254 | { | 
|  | 255 | \"repo\": \"${repo}\", | 
|  | 256 | \"@docker.manifest\": { \"\$match\" : \"${tag}*\" } | 
|  | 257 | } | 
|  | 258 | ). | 
|  | 259 | include(\"path\") | 
|  | 260 | """.stripIndent() | 
|  | 261 | withCredentials([ | 
|  | 262 | [$class: 'UsernamePasswordMultiBinding', | 
|  | 263 | credentialsId: 'artifactory', | 
|  | 264 | passwordVariable: 'ARTIFACTORY_PASSWORD', | 
|  | 265 | usernameVariable: 'ARTIFACTORY_LOGIN'] | 
|  | 266 | ]) { | 
|  | 267 | result = sh(script: "bash -c \"curl -X POST -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} -d @query  \'${url}\'\"", | 
|  | 268 | returnStdout: true).trim() | 
|  | 269 | } | 
|  | 270 | def images = new groovy.json.JsonSlurperClassic().parseText(result) | 
|  | 271 | return images.get("results") | 
|  | 272 | } | 
|  | 273 |  | 
|  | 274 | /** | 
| Alexandr Lovtsov | a029543 | 2021-01-28 17:20:32 +0300 | [diff] [blame] | 275 | * Convert Mirantis docker image url/path to Mirantis artifactory path ready for use in API calls | 
|  | 276 | * | 
|  | 277 | * For example: | 
|  | 278 | * 'docker-dev-kaas-local.docker.mirantis.net/mirantis/kaas/si-test:master' -> 'docker-dev-kaas-local/mirantis/kaas/si-test/master' | 
|  | 279 | * | 
|  | 280 | */ | 
|  | 281 | def dockerImageToArtifactoryPath(String image) { | 
|  | 282 | List imageParts = image.tokenize('/') | 
|  | 283 | String repoName = imageParts[0].tokenize('.')[0] | 
|  | 284 | String namespace = imageParts[1..-2].join('/') | 
|  | 285 | String imageName = imageParts[-1].tokenize(':')[0] | 
|  | 286 | String imageTag = imageParts[-1].tokenize(':')[1] | 
|  | 287 |  | 
|  | 288 | return [repoName, namespace, imageName, imageTag].join('/') | 
|  | 289 | } | 
|  | 290 |  | 
|  | 291 | /** | 
| Alexandr Lovtsov | 8fb5d7c | 2021-01-28 17:42:24 +0300 | [diff] [blame] | 292 | * Copy docker image from one url to another | 
|  | 293 | * | 
|  | 294 | * @param srcImage String, Mirantis URL/path for docker image to copy from | 
|  | 295 | * @param dstImage String, Mirantis URL/path for docker image to copy to | 
|  | 296 | */ | 
|  | 297 | def copyDockerImage(String srcImage, String dstImage) { | 
|  | 298 | def artifactoryServer = Artifactory.server(env.ARTIFACTORY_SERVER ?: 'mcp-ci') | 
|  | 299 | String srcPath = dockerImageToArtifactoryPath(srcImage) | 
|  | 300 | String dstPath = dockerImageToArtifactoryPath(dstImage) | 
|  | 301 |  | 
|  | 302 | return moveItem(artifactoryServer.getUrl(), srcPath, dstPath, true) | 
|  | 303 | } | 
|  | 304 |  | 
|  | 305 | /** | 
|  | 306 | * Delete docker image on Mirantis's artifactory | 
|  | 307 | * | 
|  | 308 | * @param image String, Mirantis URL/path for docker image to delete | 
|  | 309 | */ | 
|  | 310 | def deleteDockerImage(String image) { | 
|  | 311 | def artifactoryServer = Artifactory.server(env.ARTIFACTORY_SERVER ?: 'mcp-ci') | 
|  | 312 |  | 
|  | 313 | return deleteItem(artifactoryServer.getUrl() + '/artifactory', dockerImageToArtifactoryPath(image)) | 
|  | 314 | } | 
|  | 315 |  | 
|  | 316 | /** | 
| Alexandr Lovtsov | d3023d0 | 2021-05-12 15:54:44 +0300 | [diff] [blame] | 317 | * Upload list of docker images to Artifactory | 
|  | 318 | * | 
|  | 319 | * @param server ArtifactoryServer, the instance of Artifactory server | 
|  | 320 | * @param registry String, the name of Docker registry | 
|  | 321 | * @param images List[Map], list of maps where each map consist of following fields: | 
|  | 322 | *   { | 
|  | 323 | *       'repository': String '...',         // (mandatory) The name of Artifactory Docker repository | 
|  | 324 | *       'name':       String '...',         // (mandatory) docker image name | 
|  | 325 | *       'tag':        String '...',         // (mandatory) docker image tag/version | 
|  | 326 | *       'buildInfo':  BuildInfo '...',      // (optional) the instance of a build-info object which | 
|  | 327 | *                                              can be published, if it's not null (default), | 
|  | 328 | *                                              then we publish BuildInfo, | 
|  | 329 | *       'properties': LinkedHashMap '...',  // (optional) Map of artifactory properties to set for image, | 
|  | 330 | *                                              if not provided, then some common properties will be set | 
|  | 331 | *   } | 
|  | 332 | * | 
|  | 333 | */ | 
|  | 334 | def uploadImagesToArtifactory(ArtifactoryServer server, String registry, List images) { | 
|  | 335 | // Check that every provided image's specs contain mandatory fields (name, tag, repository) | 
|  | 336 | images.each { | 
|  | 337 | if (!(it.name && it.tag && it.repository)) { | 
|  | 338 | error("Incorrect image upload spec: ${it}") | 
|  | 339 | } | 
|  | 340 | } | 
|  | 341 |  | 
|  | 342 | // TODO Switch to Artifactoy image' pushing mechanism once we will | 
|  | 343 | // prepare automatical way for enabling artifactory build-proxy | 
|  | 344 | //def artDocker | 
|  | 345 | withCredentials([[ | 
|  | 346 | $class: 'UsernamePasswordMultiBinding', | 
|  | 347 | credentialsId: env.ARTIFACTORY_CREDENTIALS_ID ?: 'artifactory', | 
|  | 348 | passwordVariable: 'ARTIFACTORY_PASSWORD', | 
|  | 349 | usernameVariable: 'ARTIFACTORY_LOGIN', | 
|  | 350 | ]]) { | 
|  | 351 | sh ("docker login -u ${ARTIFACTORY_LOGIN} -p ${ARTIFACTORY_PASSWORD} ${registry}") | 
|  | 352 | //artDocker = Artifactory.docker("${env.ARTIFACTORY_LOGIN}", "${env.ARTIFACTORY_PASSWORD}") | 
|  | 353 | } | 
|  | 354 |  | 
|  | 355 | images.each { | 
|  | 356 | String image = it.name // mandatory | 
|  | 357 | String version = it.tag // mandatory | 
|  | 358 | String repository = it.repository // mandatory | 
|  | 359 |  | 
|  | 360 | sh ("docker push ${registry}/${image}:${version}") | 
|  | 361 | //artDocker.push("${registry}/${image}:${version}", repository) | 
|  | 362 | def image_url = server.getUrl() + "/api/storage/${repository}/${image}/${version}" | 
|  | 363 |  | 
|  | 364 | LinkedHashMap properties = it.get('properties') | 
|  | 365 | if ( ! properties ) { | 
|  | 366 | properties = [ | 
|  | 367 | 'com.mirantis.buildName':"${env.JOB_NAME}", | 
|  | 368 | 'com.mirantis.buildNumber': "${env.BUILD_NUMBER}", | 
|  | 369 | 'com.mirantis.gerritProject': "${env.GERRIT_PROJECT}", | 
|  | 370 | 'com.mirantis.gerritChangeNumber': "${env.GERRIT_CHANGE_NUMBER}", | 
|  | 371 | 'com.mirantis.gerritPatchsetNumber': "${env.GERRIT_PATCHSET_NUMBER}", | 
|  | 372 | 'com.mirantis.gerritChangeId': "${env.GERRIT_CHANGE_ID}", | 
|  | 373 | 'com.mirantis.gerritPatchsetRevision': "${env.GERRIT_PATCHSET_REVISION}", | 
|  | 374 | 'com.mirantis.targetImg': "${image}", | 
|  | 375 | 'com.mirantis.targetTag': "${version}" | 
|  | 376 | ] | 
|  | 377 | } | 
|  | 378 |  | 
|  | 379 | setProperties(image_url, properties) | 
|  | 380 |  | 
|  | 381 | BuildInfo buildInfo = it.get('buildInfo') | 
|  | 382 | if ( buildInfo != null ) { | 
|  | 383 | buildInfo.env.capture = true | 
|  | 384 | buildInfo.env.filter.addInclude("*") | 
|  | 385 | buildInfo.env.filter.addExclude("*PASSWORD*") | 
|  | 386 | buildInfo.env.filter.addExclude("*password*") | 
|  | 387 | buildInfo.env.collect() | 
|  | 388 | server.publishBuildInfo(buildInfo) | 
|  | 389 | } | 
|  | 390 | } | 
|  | 391 | } | 
|  | 392 | /** | 
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 393 | * Upload docker image to Artifactory | 
|  | 394 | * | 
| Sergey Kulanov | 8cd6d22 | 2016-11-17 13:42:47 +0200 | [diff] [blame] | 395 | * @param server ArtifactoryServer, the instance of Artifactory server | 
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 396 | * @param registry String, the name of Docker registry | 
|  | 397 | * @param image String, Docker image name | 
|  | 398 | * @param version String, Docker image version | 
|  | 399 | * @param repository String, The name of Artifactory Docker repository | 
| Sergey Kulanov | 8cd6d22 | 2016-11-17 13:42:47 +0200 | [diff] [blame] | 400 | * @param buildInfo BuildInfo, the instance of a build-info object which can be published, | 
|  | 401 | *                              if defined, then we publish BuildInfo | 
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 402 | */ | 
| Sergey Kulanov | 8cd6d22 | 2016-11-17 13:42:47 +0200 | [diff] [blame] | 403 | def uploadImageToArtifactory (ArtifactoryServer server, String registry, String image, | 
|  | 404 | String version, String repository, | 
| Dmitry Burmistrov | 6ee3952 | 2017-05-22 12:46:25 +0400 | [diff] [blame] | 405 | BuildInfo buildInfo = null, | 
|  | 406 | LinkedHashMap properties = null) { | 
| Alexandr Lovtsov | d3023d0 | 2021-05-12 15:54:44 +0300 | [diff] [blame] | 407 | Map images = [ | 
|  | 408 | 'repository': repository, | 
|  | 409 | 'name': image, | 
|  | 410 | 'tag': version, | 
|  | 411 | 'buildInfo': buildInfo, | 
|  | 412 | 'properties': properties, | 
|  | 413 | ] | 
|  | 414 | uploadImagesToArtifactory(server, registry, [images]) | 
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 415 | } | 
|  | 416 |  | 
|  | 417 | /** | 
|  | 418 | * Upload binaries to Artifactory | 
|  | 419 | * | 
|  | 420 | * @param server ArtifactoryServer, the instance of Artifactory server | 
|  | 421 | * @param buildInfo BuildInfo, the instance of a build-info object which can be published | 
|  | 422 | * @param uploadSpec String, a spec which is a JSON file that specifies which files should be | 
|  | 423 | *        uploaded or downloaded and the target path | 
|  | 424 | * @param publishInfo Boolean, whether publish a build-info object to Artifactory | 
|  | 425 | */ | 
| Sergey Kulanov | 91d8def | 2016-11-15 13:53:17 +0200 | [diff] [blame] | 426 | def uploadBinariesToArtifactory (ArtifactoryServer server, BuildInfo buildInfo, String uploadSpec, | 
|  | 427 | Boolean publishInfo = false) { | 
| Jakub Josef | befcf6c | 2017-11-14 18:03:10 +0100 | [diff] [blame] | 428 | server.upload(uploadSpec, buildInfo) | 
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 429 |  | 
|  | 430 | if ( publishInfo ) { | 
|  | 431 | buildInfo.env.capture = true | 
|  | 432 | buildInfo.env.filter.addInclude("*") | 
|  | 433 | buildInfo.env.filter.addExclude("*PASSWORD*") | 
|  | 434 | buildInfo.env.filter.addExclude("*password*") | 
|  | 435 | buildInfo.env.collect() | 
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 436 | server.publishBuildInfo(buildInfo) | 
|  | 437 | } | 
|  | 438 | } | 
|  | 439 |  | 
|  | 440 | /** | 
|  | 441 | * Promote Docker image artifact to release repo | 
|  | 442 | * | 
|  | 443 | * @param artifactoryURL String, an URL to Artifactory | 
|  | 444 | * @param artifactoryDevRepo String, the source dev repository name | 
|  | 445 | * @param artifactoryProdRepo String, the target repository for the move or copy | 
|  | 446 | * @param dockerRepo String, the docker repository name to promote | 
|  | 447 | * @param artifactTag String, an image tag name to promote | 
|  | 448 | * @param targetTag String, target tag to assign the image after promotion | 
|  | 449 | * @param copy Boolean, an optional value to set whether to copy instead of move | 
|  | 450 | *        Default: false | 
|  | 451 | */ | 
|  | 452 | def promoteDockerArtifact(String artifactoryURL, String artifactoryDevRepo, | 
|  | 453 | String artifactoryProdRepo, String dockerRepo, | 
|  | 454 | String artifactTag, String targetTag, Boolean copy = false) { | 
|  | 455 | def url = "${artifactoryURL}/api/docker/${artifactoryDevRepo}/v2/promote" | 
| Dmitry Burmistrov | 5deaa7d | 2017-05-30 17:12:54 +0400 | [diff] [blame] | 456 | String queryFile = UUID.randomUUID().toString() | 
| Dmitry Burmistrov | 97beb9b | 2017-05-29 17:21:34 +0400 | [diff] [blame] | 457 | writeFile file: queryFile, | 
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 458 | text: """{ | 
|  | 459 | \"targetRepo\": \"${artifactoryProdRepo}\", | 
|  | 460 | \"dockerRepository\": \"${dockerRepo}\", | 
|  | 461 | \"tag\": \"${artifactTag}\", | 
|  | 462 | \"targetTag\" : \"${targetTag}\", | 
|  | 463 | \"copy\": \"${copy}\" | 
|  | 464 | }""".stripIndent() | 
| Dmitry Burmistrov | 97beb9b | 2017-05-29 17:21:34 +0400 | [diff] [blame] | 465 | sh "cat ${queryFile}" | 
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 466 | withCredentials([ | 
|  | 467 | [$class          : 'UsernamePasswordMultiBinding', | 
|  | 468 | credentialsId   : 'artifactory', | 
|  | 469 | passwordVariable: 'ARTIFACTORY_PASSWORD', | 
|  | 470 | usernameVariable: 'ARTIFACTORY_LOGIN'] | 
|  | 471 | ]) { | 
| Sergey Reshetnyak | f0775fb | 2018-06-28 14:54:01 +0400 | [diff] [blame] | 472 | 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] | 473 | } | 
| Dmitry Burmistrov | 97beb9b | 2017-05-29 17:21:34 +0400 | [diff] [blame] | 474 | sh "rm -v ${queryFile}" | 
| Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame] | 475 | } | 
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 476 |  | 
|  | 477 | /** | 
|  | 478 | * Save job artifacts to Artifactory server if available. | 
|  | 479 | * Returns link to Artifactory repo, where saved job artifacts. | 
|  | 480 | * | 
|  | 481 | * @param config LinkedHashMap which contains next parameters: | 
|  | 482 | *   @param artifactory String, Artifactory server id | 
|  | 483 | *   @param artifactoryRepo String, repo to save job artifacts | 
|  | 484 | *   @param buildProps ArrayList, additional props for saved artifacts. Optional, default: [] | 
|  | 485 | *   @param artifactory_not_found_fail Boolean, whether to fail if provided artifactory | 
|  | 486 | *          id is not found or just print warning message. Optional, default: false | 
|  | 487 | */ | 
|  | 488 | def uploadJobArtifactsToArtifactory(LinkedHashMap config) { | 
|  | 489 | def common = new com.mirantis.mk.Common() | 
|  | 490 | def artifactsDescription = '' | 
|  | 491 | def artifactoryServer | 
| Dmitry Tyzhnenko | 9ade072 | 2020-03-31 13:17:54 +0300 | [diff] [blame] | 492 |  | 
|  | 493 | if (!config.containsKey('deleteArtifacts')) { | 
|  | 494 | config.deleteArtifacts = true  // default behavior before add the flag | 
|  | 495 | } | 
|  | 496 |  | 
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 497 | try { | 
|  | 498 | artifactoryServer = Artifactory.server(config.get('artifactory')) | 
|  | 499 | } catch (Exception e) { | 
|  | 500 | if (config.get('artifactory_not_found_fail', false)) { | 
|  | 501 | throw e | 
|  | 502 | } else { | 
|  | 503 | common.warningMsg(e) | 
|  | 504 | return "Artifactory server is not found. Can't save artifacts in Artifactory." | 
|  | 505 | } | 
|  | 506 | } | 
| Dmitry Tyzhnenko | 812673a | 2020-03-26 21:59:14 +0200 | [diff] [blame] | 507 | def artifactDir = config.get('artifactDir') ?: 'cur_build_artifacts' | 
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 508 | def user = '' | 
|  | 509 | wrap([$class: 'BuildUser']) { | 
|  | 510 | user = env.BUILD_USER_ID | 
|  | 511 | } | 
|  | 512 | dir(artifactDir) { | 
|  | 513 | try { | 
| Denis Egorenko | 5fc40f8 | 2019-03-13 18:35:51 +0400 | [diff] [blame] | 514 | unarchive(mapping: ['**/*' : '.']) | 
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 515 | // Mandatory and additional properties | 
|  | 516 | def properties = getBinaryBuildProperties(config.get('buildProps', []) << "buildUser=${user}") | 
| Dmitry Tyzhnenko | 812673a | 2020-03-26 21:59:14 +0200 | [diff] [blame] | 517 | def pattern = config.get('artifactPattern') ?: '*' | 
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 518 |  | 
|  | 519 | // Build Artifactory spec object | 
|  | 520 | def uploadSpec = """{ | 
|  | 521 | "files": | 
|  | 522 | [ | 
|  | 523 | { | 
| Dmitry Tyzhnenko | 812673a | 2020-03-26 21:59:14 +0200 | [diff] [blame] | 524 | "pattern": "${pattern}", | 
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 525 | "target": "${config.get('artifactoryRepo')}/", | 
| Denis Egorenko | 850f56a | 2019-03-13 20:44:43 +0400 | [diff] [blame] | 526 | "flat": false, | 
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 527 | "props": "${properties}" | 
|  | 528 | } | 
|  | 529 | ] | 
|  | 530 | }""" | 
|  | 531 |  | 
|  | 532 | artifactoryServer.upload(uploadSpec, newBuildInfo()) | 
|  | 533 | def linkUrl = "${artifactoryServer.getUrl()}/artifactory/${config.get('artifactoryRepo')}" | 
|  | 534 | artifactsDescription = "Job artifacts uploaded to Artifactory: <a href=\"${linkUrl}\">${linkUrl}</a>" | 
|  | 535 | } catch (Exception e) { | 
|  | 536 | if (e =~ /no artifacts/) { | 
|  | 537 | artifactsDescription = 'Build has no artifacts saved.' | 
|  | 538 | } else { | 
|  | 539 | throw e | 
|  | 540 | } | 
|  | 541 | } finally { | 
| Dmitry Tyzhnenko | 9ade072 | 2020-03-31 13:17:54 +0300 | [diff] [blame] | 542 | if (config.deleteArtifacts) { | 
|  | 543 | deleteDir() | 
|  | 544 | } | 
| Denis Egorenko | 60f47c1 | 2019-03-11 20:54:13 +0400 | [diff] [blame] | 545 | } | 
|  | 546 | } | 
|  | 547 | return artifactsDescription | 
|  | 548 | } | 
| Dmitry Tyzhnenko | 39cf09c | 2020-05-05 20:08:52 +0300 | [diff] [blame] | 549 |  | 
|  | 550 | /** | 
|  | 551 | * Save custom artifacts to Artifactory server if available. | 
|  | 552 | * Returns link to Artifactory repo, where saved artifacts. | 
|  | 553 | * | 
|  | 554 | * @param config LinkedHashMap which contains next parameters: | 
|  | 555 | *   @param artifactory String, Artifactory server id | 
|  | 556 | *   @param artifactoryRepo String, repo to save job artifacts | 
|  | 557 | *   @param buildProps ArrayList, additional props for saved artifacts. Optional, default: [] | 
|  | 558 | *   @param artifactory_not_found_fail Boolean, whether to fail if provided artifactory | 
|  | 559 | *          id is not found or just print warning message. Optional, default: false | 
|  | 560 | */ | 
|  | 561 | def uploadArtifactsToArtifactory(LinkedHashMap config) { | 
|  | 562 | def common = new com.mirantis.mk.Common() | 
|  | 563 | def artifactsDescription = '' | 
|  | 564 | def artifactoryServer | 
|  | 565 |  | 
|  | 566 | try { | 
|  | 567 | artifactoryServer = Artifactory.server(config.get('artifactory')) | 
|  | 568 | } catch (Exception e) { | 
|  | 569 | if (config.get('artifactory_not_found_fail', false)) { | 
|  | 570 | throw e | 
|  | 571 | } else { | 
|  | 572 | common.warningMsg(e) | 
|  | 573 | return "Artifactory server is not found. Can't save artifacts in Artifactory." | 
|  | 574 | } | 
|  | 575 | } | 
|  | 576 | def user = '' | 
|  | 577 | wrap([$class: 'BuildUser']) { | 
|  | 578 | user = env.BUILD_USER_ID | 
|  | 579 | } | 
|  | 580 | try { | 
|  | 581 | // Mandatory and additional properties | 
|  | 582 | def properties = getBinaryBuildProperties(config.get('buildProps', []) << "buildUser=${user}") | 
|  | 583 | def pattern = config.get('artifactPattern') ?: '*' | 
|  | 584 |  | 
|  | 585 | // Build Artifactory spec object | 
|  | 586 | def uploadSpec = """{ | 
|  | 587 | "files": | 
|  | 588 | [ | 
|  | 589 | { | 
|  | 590 | "pattern": "${pattern}", | 
|  | 591 | "target": "${config.get('artifactoryRepo')}/", | 
|  | 592 | "flat": false, | 
|  | 593 | "props": "${properties}" | 
|  | 594 | } | 
|  | 595 | ] | 
|  | 596 | }""" | 
|  | 597 |  | 
|  | 598 | artifactoryServer.upload(uploadSpec, newBuildInfo()) | 
| Alexandr Lovtsov | 9293d99 | 2021-01-19 19:55:41 +0300 | [diff] [blame] | 599 | def linkUrl = "${artifactoryServer.getUrl()}/${config.get('artifactoryRepo')}" | 
| Dmitry Tyzhnenko | 39cf09c | 2020-05-05 20:08:52 +0300 | [diff] [blame] | 600 | artifactsDescription = "Job artifacts uploaded to Artifactory: <a href=\"${linkUrl}\">${linkUrl}</a>" | 
|  | 601 | } catch (Exception e) { | 
|  | 602 | if (e =~ /no artifacts/) { | 
|  | 603 | artifactsDescription = 'Build has no artifacts saved.' | 
|  | 604 | } else { | 
|  | 605 | throw e | 
|  | 606 | } | 
|  | 607 | } | 
|  | 608 | return artifactsDescription | 
|  | 609 | } | 
| Andrii Baraniuk | f9992ca | 2023-05-04 11:43:44 +0300 | [diff] [blame] | 610 |  | 
|  | 611 | /** | 
|  | 612 | * Get artifactory server object | 
|  | 613 | * | 
|  | 614 | * @param serverName     Artifactory server name | 
|  | 615 | */ | 
|  | 616 | def getArtifactoryServer(serverName = ''){ | 
|  | 617 | if (!serverName) { | 
|  | 618 | error ("Artifactory serverName must be specified") | 
|  | 619 | } | 
|  | 620 | return Artifactory.server(serverName) | 
|  | 621 | } |