blob: 84eb14325540a20ad160ecbf7bc535638efd3071 [file] [log] [blame]
Sergey Kolekonovba203982016-12-21 18:32:17 +04001package com.mirantis.mk
2
3/**
4 *
5 * Artifactory functions
6 *
7 */
8
9/**
10 * Make generic call using Artifactory REST API and return parsed JSON
11 *
Oleksandr Hrabarba7a3d22019-07-18 17:36:30 +030012 * @param art Artifactory connection object
13 * @param uri URI which will be appended to artifactory server base URL
Sergey Kolekonovba203982016-12-21 18:32:17 +040014 * @param method HTTP method to use (default GET)
15 * @param data JSON data to POST or PUT
16 * @param headers Map of additional request headers
Oleksandr Hrabarba7a3d22019-07-18 17:36:30 +030017 * @param prefix Default prefix "/api"
Sergey Kolekonovba203982016-12-21 18:32:17 +040018 */
Oleksandr Hrabarba7a3d22019-07-18 17:36:30 +030019def restCall(art, uri, method = 'GET', data = null, headers = [:], prefix = '/api') {
20 def connection = new URL("${art.url}${prefix}${uri}").openConnection()
Sergey Kolekonovba203982016-12-21 18:32:17 +040021 if (method != 'GET') {
22 connection.setRequestMethod(method)
23 }
24
25 connection.setRequestProperty('User-Agent', 'jenkins-groovy')
26 connection.setRequestProperty('Accept', 'application/json')
27 connection.setRequestProperty('Authorization', "Basic " +
28 "${art.creds.username}:${art.creds.password}".bytes.encodeBase64().toString())
29
30 for (header in headers) {
31 connection.setRequestProperty(header.key, header.value)
32 }
33
34 if (data) {
35 connection.setDoOutput(true)
36 if (data instanceof String) {
37 connection.setRequestProperty('Content-Type', 'application/json')
38 dataStr = data
39 } else if (data instanceof java.io.File) {
40 connection.setRequestProperty('Content-Type', 'application/octet-stream')
41 dataStr = data.bytes
42 } else if (data instanceof byte[]) {
43 connection.setRequestProperty('Content-Type', 'application/octet-stream')
44 dataStr = data
45 } else {
46 connection.setRequestProperty('Content-Type', 'application/json')
47 dataStr = new groovy.json.JsonBuilder(data).toString()
48 }
49 def out = new OutputStreamWriter(connection.outputStream)
50 out.write(dataStr)
51 out.close()
52 }
53
54 if ( connection.responseCode >= 200 && connection.responseCode < 300 ) {
55 res = connection.inputStream.text
56 try {
57 return new groovy.json.JsonSlurperClassic().parseText(res)
58 } catch (Exception e) {
59 return res
60 }
61 } else {
62 throw new Exception(connection.responseCode + ": " + connection.inputStream.text)
63 }
64}
65
66/**
67 * Make GET request using Artifactory REST API and return parsed JSON
68 *
69 * @param art Artifactory connection object
70 * @param uri URI which will be appended to artifactory server base URL
71 */
72def restGet(art, uri) {
73 return restCall(art, uri)
74}
75
76/**
77 * Make PUT request using Artifactory REST API and return parsed JSON
78 *
79 * @param art Artifactory connection object
80 * @param uri URI which will be appended to artifactory server base URL
81 * @param data JSON Data to PUT
82 */
83def restPut(art, uri, data = null) {
84 return restCall(art, uri, 'PUT', data, ['Accept': '*/*'])
85}
86
87/**
88 * Make DELETE request using Artifactory REST API
89 *
90 * @param art Artifactory connection object
91 * @param uri URI which will be appended to artifactory server base URL
92 */
93def restDelete(art, uri) {
94 return restCall(art, uri, 'DELETE', null, ['Accept': '*/*'])
95}
96
97/**
98 * Make POST request using Artifactory REST API and return parsed JSON
99 *
100 * @param art Artifactory connection object
101 * @param uri URI which will be appended to artifactory server base URL
102 * @param data JSON Data to PUT
103 */
104def restPost(art, uri, data = null) {
105 return restCall(art, uri, 'POST', data, ['Accept': '*/*'])
106}
107
108/**
109 * Query artifacts by properties
110 *
Oleksandr Hrabarba7a3d22019-07-18 17:36:30 +0300111 * @param art Artifactory connection object
Sergey Kolekonovba203982016-12-21 18:32:17 +0400112 * @param properties String or list of properties in key=value format
Oleksandr Hrabarba7a3d22019-07-18 17:36:30 +0300113 * @param repo Optional repository to search in
Sergey Kolekonovba203982016-12-21 18:32:17 +0400114 */
115def findArtifactByProperties(art, properties, repo) {
116 query = parseProperties(properties)
117 if (repo) {
118 query = query + "&repos=${repo}"
119 }
120 res = restGet(art, "/search/prop?${query}")
121 return res.results
122}
123
124/**
125 * Parse properties string or map and return URL-encoded string
126 *
127 * @param properties string or key,value map
128 */
129def parseProperties(properties) {
130 if (properties instanceof String) {
131 return properties
132 } else {
133 props = []
134 for (e in properties) {
135 props.push("${e.key}=${e.value}")
136 }
137 props = props.join('|')
138 return props
139 }
140}
141
142/**
143 * Set single property or list of properties to existing artifact
144 *
145 * @param art Artifactory connection object
146 * @param name Name of artifact
147 * @param version Artifact's version, eg. Docker image tag
148 * @param properties String or list of properties in key=value format
149 * @param recursive Set properties recursively (default false)
150 */
151def setProperty(art, name, version, properties, recursive = 0) {
152 props = parseProperties(properties)
153 restPut(art, "/storage/${art.outRepo}/${name}/${version}?properties=${props}&recursive=${recursive}")
154}
155
156/**
157 * Artifactory connection and context parameters
158 *
159 * @param url Artifactory server URL
160 * @param dockerRegistryBase Base to docker registry
161 * @param dockerRegistrySSL Use https to access docker registry
162 * @param outRepo Output repository name used in context of this
163 * connection
164 * @param credentialsID ID of credentials store entry
Jakub Josef79ecec32017-02-17 14:36:28 +0100165 * @param serverName Artifactory server name (optional)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400166 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100167def connection(url, dockerRegistryBase, dockerRegistrySsl, outRepo, credentialsId = "artifactory", serverName = null) {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400168 params = [
169 "url": url,
170 "credentialsId": credentialsId,
171 "docker": [
172 "base": dockerRegistryBase,
173 "ssl": dockerRegistrySsl
174 ],
175 "outRepo": outRepo,
176 "creds": getCredentials(credentialsId)
177 ]
178
179 if (dockerRegistrySsl ?: false) {
180 params["docker"]["proto"] = "https"
181 } else {
182 params["docker"]["proto"] = "http"
183 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100184
185 if (serverName ?: null) {
186 params['server'] = Artifactory.server(serverName)
187 }
188
Sergey Kolekonovba203982016-12-21 18:32:17 +0400189 params["docker"]["url"] = "${params.docker.proto}://${params.outRepo}.${params.docker.base}"
190
191 return params
192}
193
194/**
195 * Push docker image and set artifact properties
196 *
197 * @param art Artifactory connection object
198 * @param img Docker image object
199 * @param imgName Name of docker image
200 * @param properties Map of additional artifact properties
201 * @param timestamp Build timestamp
202 * @param latest Push latest tag if set to true (default true)
203 */
204def dockerPush(art, img, imgName, properties, timestamp, latest = true) {
205 docker.withRegistry(art.docker.url, art.credentialsId) {
206 img.push()
207 // Also mark latest image
208 img.push("latest")
209 }
210
211 properties["build.number"] = currentBuild.build().environment.BUILD_NUMBER
212 properties["build.name"] = currentBuild.build().environment.JOB_NAME
213 properties["timestamp"] = timestamp
214
215 /* Set artifact properties */
216 setProperty(
217 art,
218 imgName,
219 timestamp,
220 properties
221 )
222
223 // ..and the same for latest
224 if (latest == true) {
225 setProperty(
226 art,
227 imgName,
228 "latest",
229 properties
230 )
231 }
232}
233
234/**
235 * Promote docker image to another environment
236 *
237 * @param art Artifactory connection object
238 * @param imgName Name of docker image
239 * @param tag Tag to promote
240 * @param env Environment (repository suffix) to promote to
241 * @param keep Keep artifact in source repository (copy, default true)
242 * @param latest Push latest tag if set to true (default true)
243 */
244def dockerPromote(art, imgName, tag, env, keep = true, latest = true) {
245 /* XXX: promotion this way doesn't work
246 restPost(art, "/docker/${art.outRepo}/v2/promote", [
247 "targetRepo": "${art.outRepo}-${env}",
248 "dockerRepository": imgName,
249 "tag": tag,
250 "copy": keep ? true : false
251 ])
252 */
253
254 action = keep ? "copy" : "move"
255 restPost(art, "/${action}/${art.outRepo}/${imgName}/${tag}?to=${art.outRepo}-${env}/${imgName}/${tag}")
256 if (latest == true) {
257 dockerUrl = "${art.docker.proto}://${art.outRepo}-${env}.${art.docker.base}"
258 docker.withRegistry(dockerUrl, art.credentialsId) {
259 img = docker.image("${imgName}:$tag")
260 img.pull()
261 img.push("latest")
262 }
263 }
264}
265
266/**
267 * Set offline parameter to repositories
268 *
269 * @param art Artifactory connection object
270 * @param repos List of base repositories
271 * @param suffix Suffix to append to new repository names
272 */
273def setOffline(art, repos, suffix) {
274 for (repo in repos) {
275 repoName = "${repo}-${suffix}"
276 restPost(art, "/repositories/${repoName}", ['offline': true])
277 }
278 return
279}
280
281/**
282 * Create repositories based on timestamp or other suffix from already
283 * existing repository
284 *
285 * @param art Artifactory connection object
286 * @param repos List of base repositories
287 * @param suffix Suffix to append to new repository names
288 */
289def createRepos(art, repos, suffix) {
290 def created = []
291 for (repo in repos) {
292 repoNewName = "${repo}-${suffix}"
293 repoOrig = restGet(art, "/repositories/${repo}")
294 repoOrig.key = repoNewName
295 repoNew = restPut(art, "/repositories/${repoNewName}", repoOrig)
296 created.push(repoNewName)
297 }
298 return created
299}
300
301/**
302 * Delete repositories based on timestamp or other suffix
303 *
304 * @param art Artifactory connection object
305 * @param repos List of base repositories
306 * @param suffix Suffix to append to new repository names
307 */
308def deleteRepos(art, repos, suffix) {
309 def deleted = []
310 for (repo in repos) {
311 repoName = "${repo}-${suffix}"
312 restDelete(art, "/repositories/${repoName}")
313 deleted.push(repoName)
314 }
315 return deleted
316}
317
Jakub Josef79ecec32017-02-17 14:36:28 +0100318@NonCPS
319def convertProperties(properties) {
320 return properties.collect { k,v -> "$k=$v" }.join(';')
321}
322
Sergey Kolekonovba203982016-12-21 18:32:17 +0400323/**
324 * Upload debian package
325 *
326 * @param art Artifactory connection object
327 * @param file File path
328 * @param properties Map with additional artifact properties
329 * @param timestamp Image tag
330 */
Sergey Kolekonovba203982016-12-21 18:32:17 +0400331
Jakub Josef79ecec32017-02-17 14:36:28 +0100332def uploadDebian(art, file, properties, distribution, component, timestamp) {
333 def arch = file.split('_')[-1].split('\\.')[0]
Sergey Kolekonovba203982016-12-21 18:32:17 +0400334
335 /* Set artifact properties */
336 properties["build.number"] = currentBuild.build().environment.BUILD_NUMBER
337 properties["build.name"] = currentBuild.build().environment.JOB_NAME
338 properties["timestamp"] = timestamp
Jakub Josef79ecec32017-02-17 14:36:28 +0100339
340 properties["deb.distribution"] = distribution
341 properties["deb.component"] = component
342 properties["deb.architecture"] = arch
343 props = convertProperties(properties)
344
345 def uploadSpec = """{
346 "files": [
347 {
348 "pattern": "${file}",
349 "target": "${art.outRepo}",
350 "props": "${props}"
351 }
352 ]
353 }"""
354 art.server.upload(uploadSpec)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400355}
356
357/**
358 * Build step to upload docker image. For use with eg. parallel
359 *
360 * @param art Artifactory connection object
361 * @param img Image name to push
362 * @param properties Map with additional artifact properties
363 * @param timestamp Image tag
364 */
365def uploadDockerImageStep(art, img, properties, timestamp) {
366 return {
367 println "Uploading artifact ${img} into ${art.outRepo}"
368 dockerPush(
369 art,
370 docker.image("${img}:${timestamp}"),
371 img,
372 properties,
373 timestamp
374 )
375 }
376}
377
378/**
379 * Build step to upload package. For use with eg. parallel
380 *
381 * @param art Artifactory connection object
382 * @param file File path
383 * @param properties Map with additional artifact properties
384 * @param timestamp Image tag
385 */
386def uploadPackageStep(art, file, properties, distribution, component, timestamp) {
387 return {
388 uploadDebian(
389 art,
390 file,
391 properties,
392 distribution,
393 component,
394 timestamp
395 )
396 }
397}
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300398
399/**
400 * Get Helm repo for Artifactory
401 *
402 * @param art Artifactory connection object
403 * @param repoName Chart repository name
404 */
Oleksandr Hrabar6cd0a642019-07-23 15:13:44 +0300405def getArtifactoryHelmChartRepoByName(art, repoName){
406 def res
Oleksandr Hrabarabb458a2019-07-23 18:11:12 +0300407 def common = new com.mirantis.mk.Common()
Oleksandr Hrabar6cd0a642019-07-23 15:13:44 +0300408 try {
409 res = restGet(art, "/repositories/${repoName}")
410 } catch (IOException e) {
411 def error = e.message.tokenize(':')[1]
412 if (error.contains(' 400 for URL') || error.contains(' 404 for URL')) {
413 common.warningMsg("No projects found for the pattern ${repoName} error code ${error}")
414 }else {
415 throw e
416 }
417 }
418 return res
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300419}
420
421/**
422 * Get repo by packageType for Artifactory
423 *
424 * @param art Artifactory connection object
425 * @param packageType Repository package type
426 */
Oleksandr Hrabar6cd0a642019-07-23 15:13:44 +0300427def getArtifactoryRepoByPackageType(art, repoName){
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300428 return restGet(art, "/repositories?${packageType}")
429}
430
431/**
432 * Create Helm repo for Artifactory
433 *
434 * @param art Artifactory connection object
435 * @param repoName Chart repository name
436 * @param data Transmitted data
437 */
438def createArtifactoryChartRepo(art, repoName){
439 return restPut(art, "/repositories/${repoName}", '{"rclass": "local","handleSnapshots": false,"packageType": "helm"}')
440}
441
442/**
443 * Delete Helm repo for Artifactory
444 *
445 * @param art Artifactory connection object
446 * @param repoName Chart repository name
447 */
448def deleteArtifactoryChartRepo(art, repoName){
449 return restDelete(art, "/repositories/${repoName}")
450}
451
452/**
Oleksandr Hrabar6cd0a642019-07-23 15:13:44 +0300453 * Publish Helm chart to Artifactory
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300454 *
Oleksandr Hrabar6cd0a642019-07-23 15:13:44 +0300455 * @param art Artifactory connection object from artifactory jenkins plugin
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300456 * @param repoName Repository Chart name
Oleksandr Hrabar6cd0a642019-07-23 15:13:44 +0300457 * @param chartPattern Chart pattern for publishing
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300458 */
Oleksandr Hrabar6cd0a642019-07-23 15:13:44 +0300459def publishArtifactoryHelmChart(art, repoName, chartPattern){
460 def uploadSpec = """{
461 "files": [
462 {
463 "pattern": "${chartPattern}",
Oleksandr Hrabarabb458a2019-07-23 18:11:12 +0300464 "target": "${repoName}/"
Oleksandr Hrabar6cd0a642019-07-23 15:13:44 +0300465 }
466 ]
467 }"""
468 art.upload spec: uploadSpec
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300469}
470
471/**
472 * Create Helm repo for Artifactory
473 *
474 * @param art Artifactory connection object
475 * @param repoName Repository Chart name
476 * @param chartName Chart name
477 */
478def deleteArtifactoryHelmChart(art, repoName, chartName){
Oleksandr Hrabar04049342019-07-18 11:05:22 +0000479 return restDelete(art, "/repositories/${repoName}", "${chartName}")
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300480}