Sergey Kulanov | 1bbd361 | 2016-09-30 11:40:11 +0300 | [diff] [blame] | 1 | package ci.mcp |
| 2 | |
| 3 | /** |
Ruslan Kamaldinov | 90d4e67 | 2016-11-11 18:31:00 +0300 | [diff] [blame^] | 4 | * Please do not add new functions to this file. Add them to corresponding |
| 5 | * files in com/mirantis/mcp instead. |
| 6 | **/ |
| 7 | |
| 8 | /** |
Sergey Kulanov | 1bbd361 | 2016-09-30 11:40:11 +0300 | [diff] [blame] | 9 | * https://issues.jenkins-ci.org/browse/JENKINS-26481 |
| 10 | * fix groovy List.collect() |
Sergey Kulanov | 56d0d05 | 2016-10-13 15:48:56 +0300 | [diff] [blame] | 11 | **/ |
| 12 | @NonCPS |
| 13 | def constructString(ArrayList options, String keyOption, String separator = " ") { |
| 14 | return options.collect{ keyOption + it }.join(separator).replaceAll("\n", "") |
| 15 | } |
| 16 | |
| 17 | /** |
Sergey Kulanov | 1bbd361 | 2016-09-30 11:40:11 +0300 | [diff] [blame] | 18 | * Build command line options, e.g: |
| 19 | * cmd_opts=["a=b", "c=d", "e=f"] |
| 20 | * key = "--build-arg " |
| 21 | * separator = " " |
| 22 | * def options = getCommandBuilder(cmd_opts, key, separator) |
| 23 | * println options |
| 24 | * > --build-arg a=b --build-arg c=d --build-arg e=f |
| 25 | * |
| 26 | * @param options List of Strings (options that should be populated) |
| 27 | * @param keyOption key that should be added before each option |
| 28 | * @param separator Separator between key+Option pairs |
| 29 | */ |
Sergey Kulanov | 1bbd361 | 2016-09-30 11:40:11 +0300 | [diff] [blame] | 30 | def getCommandBuilder(ArrayList options, String keyOption, String separator = " ") { |
Sergey Kulanov | 56d0d05 | 2016-10-13 15:48:56 +0300 | [diff] [blame] | 31 | return constructString(options, keyOption) |
| 32 | } |
| 33 | |
| 34 | /** |
Sergey Kulanov | 20c8b13 | 2016-11-02 13:24:32 +0200 | [diff] [blame] | 35 | * Add LABEL to the end of the Dockerfile |
| 36 | * User can also add some custom properties |
| 37 | * |
| 38 | * @param dockerfilePath is the path to Dockerfile, the default is ./Dockerfile |
| 39 | * @param customProperties a Array of Strings that should be added to mandatory props |
| 40 | * in format ["prop1=value1", "prop2=value2"] |
| 41 | **/ |
| 42 | def setDockerfileLabels(String dockerfilePath = "./Dockerfile", ArrayList customProperties = null){ |
| 43 | |
| 44 | if (!fileExists(dockerfilePath)){ |
| 45 | throw new RuntimeException("Unable to add LABEL to Dockerfile, ${dockerfilePath} doesn't exists") |
| 46 | } |
| 47 | echo "Updating ${dockerfilePath}" |
| 48 | |
| 49 | def namespace = "com.mirantis.image-specs." |
| 50 | def properties = [ |
| 51 | "gerritProject=${env.GERRIT_PROJECT}", |
| 52 | "gerritChangeNumber=${env.GERRIT_CHANGE_NUMBER}", |
| 53 | "gerritPatchsetNumber=${env.GERRIT_PATCHSET_NUMBER}", |
| 54 | "gerritChangeId=${env.GERRIT_CHANGE_ID}", |
| 55 | "gerritPatchsetRevision=${env.GERRIT_PATCHSET_REVISION}" |
| 56 | ] |
| 57 | |
| 58 | if (customProperties != null){ |
| 59 | properties.addAll(customProperties) |
| 60 | } |
| 61 | |
| 62 | def metadata = constructString(properties, namespace, " ") |
| 63 | sh """ |
| 64 | cat <<EOF>> ${dockerfilePath} |
| 65 | # Apply additional build metadata |
| 66 | LABEL ${metadata} |
| 67 | """ |
| 68 | return metadata |
| 69 | } |
| 70 | |
| 71 | /** |
Sergey Kulanov | 56d0d05 | 2016-10-13 15:48:56 +0300 | [diff] [blame] | 72 | * Return string of mandatory build properties for binaries |
| 73 | * User can also add some custom properties |
| 74 | * |
| 75 | * @param customProperties a Array of Strings that should be added to mandatory props |
| 76 | * in format ["prop1=value1", "prop2=value2"] |
| 77 | **/ |
| 78 | def getBinaryBuildProperties(ArrayList customProperties) { |
| 79 | |
| 80 | def namespace = "com.mirantis." |
| 81 | def properties = [ |
| 82 | "gerritProject=${env.GERRIT_PROJECT}", |
| 83 | "gerritChangeNumber=${env.GERRIT_CHANGE_NUMBER}", |
| 84 | "gerritPatchsetNumber=${env.GERRIT_PATCHSET_NUMBER}", |
| 85 | "gerritChangeId=${env.GERRIT_CHANGE_ID}", |
Sergey Kulanov | 64bc88a | 2016-10-18 16:26:34 +0300 | [diff] [blame] | 86 | "gerritPatchsetRevision=${env.GERRIT_PATCHSET_REVISION}" |
Sergey Kulanov | 56d0d05 | 2016-10-13 15:48:56 +0300 | [diff] [blame] | 87 | ] |
| 88 | |
| 89 | if (customProperties){ |
| 90 | properties.addAll(customProperties) |
| 91 | } |
| 92 | |
| 93 | return constructString(properties, namespace, ";") |
Sergey Kulanov | 1bbd361 | 2016-09-30 11:40:11 +0300 | [diff] [blame] | 94 | } |
Sergey Kulanov | 1835afe | 2016-10-19 16:53:14 +0300 | [diff] [blame] | 95 | |
| 96 | /** |
| 97 | * Parse HEAD of current directory and return commit hash |
| 98 | */ |
| 99 | def getGitCommit() { |
| 100 | git_commit = sh ( |
| 101 | script: 'git rev-parse HEAD', |
| 102 | returnStdout: true |
| 103 | ).trim() |
| 104 | return git_commit |
| 105 | } |
Denis Egorenko | e355268 | 2016-10-18 13:29:29 +0300 | [diff] [blame] | 106 | |
| 107 | /** |
Alexander Saprykin | b28156c | 2016-11-09 11:09:48 +0100 | [diff] [blame] | 108 | * Describe a commit using the most recent tag reachable from it |
| 109 | */ |
| 110 | def getGitDescribe() { |
| 111 | git_commit = sh ( |
| 112 | script: 'git describe --tags', |
| 113 | returnStdout: true |
| 114 | ).trim() |
| 115 | return git_commit |
| 116 | } |
| 117 | |
| 118 | /** |
Sergey Kulanov | 4f2fbcb | 2016-10-28 14:25:20 +0300 | [diff] [blame] | 119 | * Generate current timestamp |
| 120 | * |
| 121 | * @param format Defaults to yyyyMMddHHmmss |
| 122 | */ |
| 123 | def getDatetime(format="yyyyMMddHHmmss") { |
| 124 | def now = new Date(); |
| 125 | return now.format(format, TimeZone.getTimeZone('UTC')); |
| 126 | } |
| 127 | |
| 128 | /** |
Denis Egorenko | e355268 | 2016-10-18 13:29:29 +0300 | [diff] [blame] | 129 | * Get URL to artifact by properties |
| 130 | * Returns String with URL to found artifact or null if nothing |
| 131 | * |
| 132 | * @param artifactoryURL String, an URL to Artifactory |
| 133 | * @param properties LinkedHashMap, a Hash of properties (key-value) which |
| 134 | * which should determine artifact in Artifactory |
| 135 | */ |
| 136 | def uriByProperties(String artifactoryURL, LinkedHashMap properties) { |
| 137 | def key, value |
| 138 | def properties_str = '' |
| 139 | for ( int i = 0; i < properties.size(); i++ ) { |
| 140 | // avoid serialization errors |
| 141 | key = properties.entrySet().toArray()[i].key |
| 142 | value = properties.entrySet().toArray()[i].value |
| 143 | properties_str += "${key}=${value}&" |
| 144 | } |
| 145 | def search_url = "${artifactoryURL}/api/search/prop?${properties_str}" |
| 146 | |
| 147 | def result = sh(script: "bash -c \"curl -X GET \'${search_url}\'\"", |
| 148 | returnStdout: true).trim() |
| 149 | def content = new groovy.json.JsonSlurperClassic().parseText(result) |
| 150 | def uri = content.get("results") |
| 151 | if ( uri ) { |
| 152 | return uri.last().get("uri") |
| 153 | } else { |
| 154 | return null |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | /** |
Sergey Kulanov | d4e3128 | 2016-11-08 17:39:19 +0200 | [diff] [blame] | 159 | * Get URL to artifact by properties |
| 160 | * Returns String with URL to found artifact or null if nothing |
| 161 | * |
| 162 | * @param artifactoryURL String, an URL to Artifactory |
| 163 | * @param properties String, URI in format prop1=val1&prop2=val2&prop3val3 |
| 164 | * which should determine artifact in Artifactory |
| 165 | */ |
| 166 | def uriByProperties(String artifactoryURL, String properties) { |
| 167 | |
| 168 | def search_url = "${artifactoryURL}/api/search/prop?${properties}" |
| 169 | |
| 170 | def result = sh(script: "bash -c \"curl -X GET \'${search_url}\'\"", |
| 171 | returnStdout: true).trim() |
| 172 | def content = new groovy.json.JsonSlurperClassic().parseText(result) |
| 173 | def uri = content.get("results") |
| 174 | if ( uri ) { |
| 175 | return uri.last().get("uri") |
| 176 | } else { |
| 177 | return null |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | /** |
Denis Egorenko | e355268 | 2016-10-18 13:29:29 +0300 | [diff] [blame] | 182 | * Set properties for artifact in Artifactory repo |
| 183 | * |
| 184 | * @param artifactUrl String, an URL to artifact in Artifactory repo |
| 185 | * @param properties LinkedHashMap, a Hash of properties (key-value) which |
| 186 | * should be assigned for choosen artifact |
| 187 | * @param recursive Boolean, if artifact_url is a directory, whether to set |
| 188 | * properties recursively or not |
| 189 | */ |
| 190 | def setProperties (String artifactUrl, LinkedHashMap properties, Boolean recursive=false) { |
| 191 | def properties_str = 'properties=' |
| 192 | def key,value |
| 193 | if (recursive) { |
| 194 | recursive = 'recursive=1' |
| 195 | } else { |
| 196 | recursive = 'recursive=0' |
| 197 | } |
| 198 | for ( int i = 0; i < properties.size(); i++ ) { |
| 199 | // avoid serialization errors |
| 200 | key = properties.entrySet().toArray()[i].key |
| 201 | value = properties.entrySet().toArray()[i].value |
| 202 | properties_str += "${key}=${value}|" |
| 203 | } |
| 204 | def url = "${artifactUrl}?${properties_str}&${recursive}" |
| 205 | withCredentials([ |
| 206 | [$class: 'UsernamePasswordMultiBinding', |
| 207 | credentialsId: 'artifactory', |
| 208 | passwordVariable: 'ARTIFACTORY_PASSWORD', |
| 209 | usernameVariable: 'ARTIFACTORY_LOGIN'] |
| 210 | ]) { |
| 211 | sh "bash -c \"curl -X PUT -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} \'${url}\'\"" |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | /** |
| 216 | * Get properties for specified artifact in Artifactory |
| 217 | * Returns LinkedHashMap of properties |
| 218 | * |
| 219 | * @param artifactUrl String, an URL to artifact in Artifactory repo |
| 220 | */ |
| 221 | def getPropertiesForArtifact(String artifactUrl) { |
| 222 | def url = "${artifactUrl}?properties" |
| 223 | def result |
| 224 | withCredentials([ |
| 225 | [$class: 'UsernamePasswordMultiBinding', |
| 226 | credentialsId: 'artifactory', |
| 227 | passwordVariable: 'ARTIFACTORY_PASSWORD', |
| 228 | usernameVariable: 'ARTIFACTORY_LOGIN'] |
| 229 | ]) { |
| 230 | result = sh(script: "bash -c \"curl -X GET -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} \'${url}\'\"", |
| 231 | returnStdout: true).trim() |
| 232 | } |
| 233 | def properties = new groovy.json.JsonSlurperClassic().parseText(result) |
| 234 | return properties.get("properties") |
| 235 | } |
| 236 | |
| 237 | /** |
| 238 | * Upload docker image to Artifactory |
| 239 | * |
| 240 | * @param artifactoryURL String, an URL to Artifactory |
| 241 | * @param registry String, the name of Docker registry |
| 242 | * @param image String, Docker image name |
| 243 | * @param version String, Docker image version |
| 244 | * @param repository String, The name of Artifactory Docker repository |
| 245 | */ |
| 246 | def uploadImageToArtifactory (String artifactoryURL, String registry, String image, |
| 247 | String version, String repository) { |
| 248 | // TODO Switch to Artifactoy image' pushing mechanism once we will |
| 249 | // prepare automatical way for enabling artifactory build-proxy |
| 250 | //def artDocker |
| 251 | withCredentials([ |
| 252 | [$class: 'UsernamePasswordMultiBinding', |
| 253 | credentialsId: 'artifactory', |
| 254 | passwordVariable: 'ARTIFACTORY_PASSWORD', |
| 255 | usernameVariable: 'ARTIFACTORY_LOGIN'] |
| 256 | ]) { |
| 257 | sh ("docker login -u ${ARTIFACTORY_LOGIN} -p ${ARTIFACTORY_PASSWORD} ${registry}") |
| 258 | //artDocker = Artifactory.docker("${env.ARTIFACTORY_LOGIN}", "${env.ARTIFACTORY_PASSWORD}") |
| 259 | } |
| 260 | |
| 261 | sh ("docker push ${registry}/${image}:${version}") |
| 262 | //artDocker.push("${registry}/${image}:${version}", "${repository}") |
| 263 | def image_url = "${artifactoryURL}/api/storage/${repository}/${image}/${version}" |
| 264 | |
| 265 | def properties = ['com.mirantis.build_name':"${env.JOB_NAME}", |
| 266 | 'com.mirantis.build_id': "${env.BUILD_NUMBER}", |
| 267 | 'com.mirantis.changeid': "${env.GERRIT_CHANGE_ID}", |
| 268 | 'com.mirantis.patchset_number': "${env.GERRIT_PATCHSET_NUMBER}", |
| 269 | 'com.mirantis.target_tag': "${version}"] |
| 270 | setProperties(image_url, properties) |
| 271 | } |
| 272 | |
| 273 | /** |
| 274 | * Upload binaries to Artifactory |
| 275 | * |
| 276 | * @param server ArtifactoryServer, the instance of Artifactory server |
| 277 | * @param buildInfo BuildInfo, the instance of a build-info object which can be published |
| 278 | * @param uploadSpec String, a spec which is a JSON file that specifies which files should be |
| 279 | * uploaded or downloaded and the target path |
| 280 | * @param publishInfo Boolean, whether publish a build-info object to Artifactory |
| 281 | */ |
| 282 | def uploadBinariesToArtifactory (server, buildInfo, String uploadSpec, |
| 283 | Boolean publishInfo=false) { |
| 284 | buildInfo.append(server.upload(uploadSpec)) |
| 285 | |
| 286 | if ( publishInfo ) { |
| 287 | buildInfo.env.capture = true |
| 288 | buildInfo.env.filter.addInclude("*") |
| 289 | buildInfo.env.filter.addExclude("*PASSWORD*") |
| 290 | buildInfo.env.filter.addExclude("*password*") |
| 291 | buildInfo.env.collect() |
| 292 | server.publishBuildInfo(buildInfo) |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | /** |
| 297 | * Promote Docker image artifact to release repo |
| 298 | * |
| 299 | * @param artifactoryURL String, an URL to Artifactory |
| 300 | * @param artifactoryDevRepo String, the source dev repository name |
| 301 | * @param artifactoryProdRepo String, the target repository for the move or copy |
| 302 | * @param dockerRepo String, the docker repository name to promote |
| 303 | * @param artifactTag String, an image tag name to promote |
| 304 | * @param targetTag String, target tag to assign the image after promotion |
| 305 | * @param copy Boolean, an optional value to set whether to copy instead of move |
| 306 | * Default: false |
| 307 | */ |
| 308 | def promoteDockerArtifact(String artifactoryURL, String artifactoryDevRepo, |
| 309 | String artifactoryProdRepo, String dockerRepo, |
| 310 | String artifactTag, String targetTag, Boolean copy=false) { |
| 311 | def url = "${artifactoryURL}/api/docker/${artifactoryDevRepo}/v2/promote" |
| 312 | writeFile file: "query.json", |
| 313 | text: """{ |
| 314 | \"targetRepo\": \"${artifactoryProdRepo}\", |
| 315 | \"dockerRepository\": \"${dockerRepo}\", |
| 316 | \"tag\": \"${artifactTag}\", |
| 317 | \"targetTag\" : \"${targetTag}\", |
| 318 | \"copy\": \"${copy}\" |
| 319 | }""".stripIndent() |
| 320 | sh "cat query.json" |
| 321 | withCredentials([ |
| 322 | [$class: 'UsernamePasswordMultiBinding', |
| 323 | credentialsId: 'artifactory', |
| 324 | passwordVariable: 'ARTIFACTORY_PASSWORD', |
| 325 | usernameVariable: 'ARTIFACTORY_LOGIN'] |
| 326 | ]) { |
| 327 | sh "bash -c \"curl -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} -H \"Content-Type:application/json\" -X POST -d @query.json ${url}\"" |
| 328 | } |
| 329 | } |