blob: d253704b47ccb518c2bbf2de8a8cb5d3356d9331 [file] [log] [blame]
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +03001package com.mirantis.mcp
2
Sergey Kulanov91d8def2016-11-15 13:53:17 +02003import org.jfrog.hudson.pipeline.types.ArtifactoryServer
4import org.jfrog.hudson.pipeline.types.buildInfo.BuildInfo
5
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +03006/**
7 * Return string of mandatory build properties for binaries
8 * User can also add some custom properties.
9 *
10 * @param customProperties a Array of Strings that should be added to mandatory props
11 * in format ["prop1=value1", "prop2=value2"]
12 * */
13def getBinaryBuildProperties(ArrayList customProperties) {
14 def namespace = "com.mirantis."
15 def properties = [
Sergey Kulanovc70f1c22016-11-16 13:05:20 +020016 "buildName=${env.JOB_NAME}",
17 "buildNumber=${env.BUILD_NUMBER}",
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030018 "gerritProject=${env.GERRIT_PROJECT}",
19 "gerritChangeNumber=${env.GERRIT_CHANGE_NUMBER}",
20 "gerritPatchsetNumber=${env.GERRIT_PATCHSET_NUMBER}",
21 "gerritChangeId=${env.GERRIT_CHANGE_ID}",
22 "gerritPatchsetRevision=${env.GERRIT_PATCHSET_REVISION}"
23 ]
24
25 if (customProperties) {
26 properties.addAll(customProperties)
27 }
28
29 def common = new com.mirantis.mcp.Common()
30
31 return common.constructString(properties, namespace, ";")
32}
33
34/**
Kirill Mashchenko1d225c22018-06-19 13:52:17 +030035 * Get URL to artifact(s) by properties
36 * Returns String(s) with URL to found artifact or null if nothing
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030037 *
38 * @param artifactoryURL String, an URL to Artifactory
39 * @param properties LinkedHashMap, a Hash of properties (key-value) which
40 * which should determine artifact in Artifactory
Kirill Mashchenko1d225c22018-06-19 13:52:17 +030041 * @param onlyLastItem Boolean, return only last URL if true(by default),
42 * else return list of all found artifact URLS
43 *
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030044 */
Kirill Mashchenko1d225c22018-06-19 13:52:17 +030045def uriByProperties(String artifactoryURL, LinkedHashMap properties, Boolean onlyLastItem=true) {
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030046 def key, value
47 def properties_str = ''
48 for (int i = 0; i < properties.size(); i++) {
49 // avoid serialization errors
Kirill Mashchenko56c8ff32018-06-28 03:01:34 +030050 key = properties.entrySet().toArray()[i].key.trim()
51 value = properties.entrySet().toArray()[i].value.trim()
52 properties_str += /${key}=${value}&/
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030053 }
54 def search_url = "${artifactoryURL}/api/search/prop?${properties_str}"
55
Kirill Mashchenko56c8ff32018-06-28 03:01:34 +030056 def result = sh(script: /curl -X GET '${search_url}'/,
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030057 returnStdout: true).trim()
58 def content = new groovy.json.JsonSlurperClassic().parseText(result)
59 def uri = content.get("results")
60 if (uri) {
Kirill Mashchenko1d225c22018-06-19 13:52:17 +030061 if (onlyLastItem) {
62 return uri.last().get("uri")
63 } else {
64 res = []
65 uri.each {it ->
66 res.add(it.get("uri"))
67 }
68 return res
69 }
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030070 } else {
71 return null
72 }
73}
74
Kirill Mashchenko1d225c22018-06-19 13:52:17 +030075
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030076/**
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030077 * Set properties for artifact in Artifactory repo
78 *
79 * @param artifactUrl String, an URL to artifact in Artifactory repo
80 * @param properties LinkedHashMap, a Hash of properties (key-value) which
81 * should be assigned for choosen artifact
82 * @param recursive Boolean, if artifact_url is a directory, whether to set
83 * properties recursively or not
84 */
85def setProperties(String artifactUrl, LinkedHashMap properties, Boolean recursive = false) {
86 def properties_str = 'properties='
87 def key, value
88 if (recursive) {
89 recursive = 'recursive=1'
90 } else {
91 recursive = 'recursive=0'
92 }
Alexander Evseevbd40ef92017-10-18 12:24:45 +030093 properties_str += properties.collect({"${it.key}=${it.value}"}).join(';')
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +030094 def url = "${artifactUrl}?${properties_str}&${recursive}"
95 withCredentials([
96 [$class : 'UsernamePasswordMultiBinding',
97 credentialsId : 'artifactory',
98 passwordVariable: 'ARTIFACTORY_PASSWORD',
99 usernameVariable: 'ARTIFACTORY_LOGIN']
100 ]) {
101 sh "bash -c \"curl -X PUT -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} \'${url}\'\""
102 }
103}
104
105/**
106 * Get properties for specified artifact in Artifactory
107 * Returns LinkedHashMap of properties
108 *
109 * @param artifactUrl String, an URL to artifact in Artifactory repo
110 */
111def getPropertiesForArtifact(String artifactUrl) {
112 def url = "${artifactUrl}?properties"
113 def result
114 withCredentials([
115 [$class : 'UsernamePasswordMultiBinding',
116 credentialsId : 'artifactory',
117 passwordVariable: 'ARTIFACTORY_PASSWORD',
118 usernameVariable: 'ARTIFACTORY_LOGIN']
119 ]) {
120 result = sh(script: "bash -c \"curl -X GET -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} \'${url}\'\"",
121 returnStdout: true).trim()
122 }
123 def properties = new groovy.json.JsonSlurperClassic().parseText(result)
124 return properties.get("properties")
125}
126
127/**
Denis Egorenkoedd21dc2018-11-23 17:38:17 +0400128 * Check if image with tag exist by provided path
129 * Returns true or false
130 *
131 * @param artifactoryURL String, an URL to Artifactory
132 * @param imageRepo String, path to image to check, includes repo path and image name
133 * @param tag String, tag to check
134 * @param artifactoryCreds String, artifactory creds to use. Optional, default is 'artifactory'
135 */
136def imageExists(String artifactoryURL, String imageRepo, String tag, String artifactoryCreds = 'artifactory') {
137 def url = artifactoryURL + '/v2/' + imageRepo + '/manifest/' + tag
138 def result
139 withCredentials([
140 [$class : 'UsernamePasswordMultiBinding',
141 credentialsId : artifactoryCreds,
142 passwordVariable: 'ARTIFACTORY_PASSWORD',
143 usernameVariable: 'ARTIFACTORY_LOGIN']
144 ]) {
145 result = sh(script: "bash -c \"curl -X GET -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} \'${url}\'\"",
146 returnStdout: true).trim()
147 }
148 def properties = new groovy.json.JsonSlurperClassic().parseText(result)
149 return properties.get("errors") ? false : true
150}
151
152/**
Denis Egorenko7c0abfe2017-02-14 15:42:02 +0400153 * Find docker images by tag
154 * Returns Array of image' hashes with names as full path in @repo
155 *
156 * Example:
157 *
158 * [ {
159 * "path" : "mirantis/ccp/ci-cd/gerrit-manage/test"
160 * },
161 * {
162 * "path" : "mirantis/ccp/ci-cd/gerrit/test"
163 * }
164 * ]
165 *
166 * @param artifactoryURL String, an URL to Artifactory
167 * @param repo String, a name of repo where should be executed search
168 * @param tag String, tag of searched image
169 */
170def getImagesByTag(String artifactoryURL, String repo, String tag) {
171 def url = "${artifactoryURL}/api/search/aql"
172 def result
173 writeFile file: "query",
174 text: """\
175 items.find(
176 {
177 \"repo\": \"${repo}\",
178 \"@docker.manifest\": { \"\$match\" : \"${tag}*\" }
179 }
180 ).
181 include(\"path\")
182 """.stripIndent()
183 withCredentials([
184 [$class: 'UsernamePasswordMultiBinding',
185 credentialsId: 'artifactory',
186 passwordVariable: 'ARTIFACTORY_PASSWORD',
187 usernameVariable: 'ARTIFACTORY_LOGIN']
188 ]) {
189 result = sh(script: "bash -c \"curl -X POST -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} -d @query \'${url}\'\"",
190 returnStdout: true).trim()
191 }
192 def images = new groovy.json.JsonSlurperClassic().parseText(result)
193 return images.get("results")
194}
195
196/**
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300197 * Upload docker image to Artifactory
198 *
Sergey Kulanov8cd6d222016-11-17 13:42:47 +0200199 * @param server ArtifactoryServer, the instance of Artifactory server
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300200 * @param registry String, the name of Docker registry
201 * @param image String, Docker image name
202 * @param version String, Docker image version
203 * @param repository String, The name of Artifactory Docker repository
Sergey Kulanov8cd6d222016-11-17 13:42:47 +0200204 * @param buildInfo BuildInfo, the instance of a build-info object which can be published,
205 * if defined, then we publish BuildInfo
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300206 */
Sergey Kulanov8cd6d222016-11-17 13:42:47 +0200207def uploadImageToArtifactory (ArtifactoryServer server, String registry, String image,
208 String version, String repository,
Dmitry Burmistrov6ee39522017-05-22 12:46:25 +0400209 BuildInfo buildInfo = null,
210 LinkedHashMap properties = null) {
Denis Egorenkoedba5a52016-11-15 19:55:56 +0300211 // TODO Switch to Artifactoy image' pushing mechanism once we will
212 // prepare automatical way for enabling artifactory build-proxy
213 //def artDocker
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300214 withCredentials([
215 [$class: 'UsernamePasswordMultiBinding',
216 credentialsId: 'artifactory',
217 passwordVariable: 'ARTIFACTORY_PASSWORD',
218 usernameVariable: 'ARTIFACTORY_LOGIN']
219 ]) {
220 sh ("docker login -u ${ARTIFACTORY_LOGIN} -p ${ARTIFACTORY_PASSWORD} ${registry}")
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300221 //artDocker = Artifactory.docker("${env.ARTIFACTORY_LOGIN}", "${env.ARTIFACTORY_PASSWORD}")
222 }
223
Denis Egorenkoedba5a52016-11-15 19:55:56 +0300224 sh ("docker push ${registry}/${image}:${version}")
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300225 //artDocker.push("${registry}/${image}:${version}", "${repository}")
Sergey Kulanov8cd6d222016-11-17 13:42:47 +0200226 def image_url = server.getUrl() + "/api/storage/${repository}/${image}/${version}"
Dmitry Burmistrov6ee39522017-05-22 12:46:25 +0400227 if ( ! properties ) {
228 properties = [
Sergey Kulanovc70f1c22016-11-16 13:05:20 +0200229 'com.mirantis.buildName':"${env.JOB_NAME}",
230 'com.mirantis.buildNumber': "${env.BUILD_NUMBER}",
231 'com.mirantis.gerritProject': "${env.GERRIT_PROJECT}",
232 'com.mirantis.gerritChangeNumber': "${env.GERRIT_CHANGE_NUMBER}",
233 'com.mirantis.gerritPatchsetNumber': "${env.GERRIT_PATCHSET_NUMBER}",
234 'com.mirantis.gerritChangeId': "${env.GERRIT_CHANGE_ID}",
235 'com.mirantis.gerritPatchsetRevision': "${env.GERRIT_PATCHSET_REVISION}",
Sergey Kulanov4d3951c2016-11-24 13:58:15 +0200236 'com.mirantis.targetImg': "${image}",
Sergey Kulanovc70f1c22016-11-16 13:05:20 +0200237 'com.mirantis.targetTag': "${version}"
Dmitry Burmistrov6ee39522017-05-22 12:46:25 +0400238 ]
239 }
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300240
241 setProperties(image_url, properties)
Sergey Kulanov8cd6d222016-11-17 13:42:47 +0200242
243 if ( buildInfo != null ) {
244 buildInfo.env.capture = true
245 buildInfo.env.filter.addInclude("*")
246 buildInfo.env.filter.addExclude("*PASSWORD*")
247 buildInfo.env.filter.addExclude("*password*")
248 buildInfo.env.collect()
249 server.publishBuildInfo(buildInfo)
250 }
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300251}
252
253/**
254 * Upload binaries to Artifactory
255 *
256 * @param server ArtifactoryServer, the instance of Artifactory server
257 * @param buildInfo BuildInfo, the instance of a build-info object which can be published
258 * @param uploadSpec String, a spec which is a JSON file that specifies which files should be
259 * uploaded or downloaded and the target path
260 * @param publishInfo Boolean, whether publish a build-info object to Artifactory
261 */
Sergey Kulanov91d8def2016-11-15 13:53:17 +0200262def uploadBinariesToArtifactory (ArtifactoryServer server, BuildInfo buildInfo, String uploadSpec,
263 Boolean publishInfo = false) {
Jakub Josefbefcf6c2017-11-14 18:03:10 +0100264 server.upload(uploadSpec, buildInfo)
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300265
266 if ( publishInfo ) {
267 buildInfo.env.capture = true
268 buildInfo.env.filter.addInclude("*")
269 buildInfo.env.filter.addExclude("*PASSWORD*")
270 buildInfo.env.filter.addExclude("*password*")
271 buildInfo.env.collect()
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300272 server.publishBuildInfo(buildInfo)
273 }
274}
275
276/**
277 * Promote Docker image artifact to release repo
278 *
279 * @param artifactoryURL String, an URL to Artifactory
280 * @param artifactoryDevRepo String, the source dev repository name
281 * @param artifactoryProdRepo String, the target repository for the move or copy
282 * @param dockerRepo String, the docker repository name to promote
283 * @param artifactTag String, an image tag name to promote
284 * @param targetTag String, target tag to assign the image after promotion
285 * @param copy Boolean, an optional value to set whether to copy instead of move
286 * Default: false
287 */
288def promoteDockerArtifact(String artifactoryURL, String artifactoryDevRepo,
289 String artifactoryProdRepo, String dockerRepo,
290 String artifactTag, String targetTag, Boolean copy = false) {
291 def url = "${artifactoryURL}/api/docker/${artifactoryDevRepo}/v2/promote"
Dmitry Burmistrov5deaa7d2017-05-30 17:12:54 +0400292 String queryFile = UUID.randomUUID().toString()
Dmitry Burmistrov97beb9b2017-05-29 17:21:34 +0400293 writeFile file: queryFile,
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300294 text: """{
295 \"targetRepo\": \"${artifactoryProdRepo}\",
296 \"dockerRepository\": \"${dockerRepo}\",
297 \"tag\": \"${artifactTag}\",
298 \"targetTag\" : \"${targetTag}\",
299 \"copy\": \"${copy}\"
300 }""".stripIndent()
Dmitry Burmistrov97beb9b2017-05-29 17:21:34 +0400301 sh "cat ${queryFile}"
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300302 withCredentials([
303 [$class : 'UsernamePasswordMultiBinding',
304 credentialsId : 'artifactory',
305 passwordVariable: 'ARTIFACTORY_PASSWORD',
306 usernameVariable: 'ARTIFACTORY_LOGIN']
307 ]) {
Sergey Reshetnyakf0775fb2018-06-28 14:54:01 +0400308 sh "bash -c \"curl --fail -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} -H \"Content-Type:application/json\" -X POST -d @${queryFile} ${url}\""
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300309 }
Dmitry Burmistrov97beb9b2017-05-29 17:21:34 +0400310 sh "rm -v ${queryFile}"
Ruslan Kamaldinov90d4e672016-11-11 18:31:00 +0300311}
Denis Egorenko60f47c12019-03-11 20:54:13 +0400312
313/**
314 * Save job artifacts to Artifactory server if available.
315 * Returns link to Artifactory repo, where saved job artifacts.
316 *
317 * @param config LinkedHashMap which contains next parameters:
318 * @param artifactory String, Artifactory server id
319 * @param artifactoryRepo String, repo to save job artifacts
320 * @param buildProps ArrayList, additional props for saved artifacts. Optional, default: []
321 * @param artifactory_not_found_fail Boolean, whether to fail if provided artifactory
322 * id is not found or just print warning message. Optional, default: false
323 */
324def uploadJobArtifactsToArtifactory(LinkedHashMap config) {
325 def common = new com.mirantis.mk.Common()
326 def artifactsDescription = ''
327 def artifactoryServer
328 try {
329 artifactoryServer = Artifactory.server(config.get('artifactory'))
330 } catch (Exception e) {
331 if (config.get('artifactory_not_found_fail', false)) {
332 throw e
333 } else {
334 common.warningMsg(e)
335 return "Artifactory server is not found. Can't save artifacts in Artifactory."
336 }
337 }
338 def artifactDir = 'cur_build_artifacts'
339 def user = ''
340 wrap([$class: 'BuildUser']) {
341 user = env.BUILD_USER_ID
342 }
343 dir(artifactDir) {
344 try {
345 unarchive(mapping: ['*' : '.'])
346 // Mandatory and additional properties
347 def properties = getBinaryBuildProperties(config.get('buildProps', []) << "buildUser=${user}")
348
349 // Build Artifactory spec object
350 def uploadSpec = """{
351 "files":
352 [
353 {
354 "pattern": "*",
355 "target": "${config.get('artifactoryRepo')}/",
356 "props": "${properties}"
357 }
358 ]
359 }"""
360
361 artifactoryServer.upload(uploadSpec, newBuildInfo())
362 def linkUrl = "${artifactoryServer.getUrl()}/artifactory/${config.get('artifactoryRepo')}"
363 artifactsDescription = "Job artifacts uploaded to Artifactory: <a href=\"${linkUrl}\">${linkUrl}</a>"
364 } catch (Exception e) {
365 if (e =~ /no artifacts/) {
366 artifactsDescription = 'Build has no artifacts saved.'
367 } else {
368 throw e
369 }
370 } finally {
371 deleteDir()
372 }
373 }
374 return artifactsDescription
375}