blob: 6705b4bfeead5b34578836e1a4dbd54cfe46c77f [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}",
Vasyl Saienko4d08c442021-01-18 11:44:03 +0200349 "target": "artifactory/${art.outRepo}",
Jakub Josef79ecec32017-02-17 14:36:28 +0100350 "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/**
Sergey Otpuschennikov6aa23ec2019-10-07 18:57:58 +0400432 * Get checksums of artifact
433 *
434 * @param art Artifactory connection object
435 * @param artifactName Artifactory object name
436 * @param checksum Type of checksum (default md5)
437 * @param repoName Artifact repository name
438 */
439
440def getArtifactChecksum(art, repoName, artifactName, checksum = 'md5'){
441 def artifactory = new com.mirantis.mk.Artifactory()
442 def uri = "/storage/${repoName}/${artifactName}"
443 def output = artifactory.restGet(art, uri)
444 return output['checksums']["${checksum}"]
445}
446
447/**
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300448 * Create Helm repo for Artifactory
449 *
450 * @param art Artifactory connection object
451 * @param repoName Chart repository name
452 * @param data Transmitted data
453 */
454def createArtifactoryChartRepo(art, repoName){
455 return restPut(art, "/repositories/${repoName}", '{"rclass": "local","handleSnapshots": false,"packageType": "helm"}')
456}
457
458/**
459 * Delete Helm repo for Artifactory
460 *
461 * @param art Artifactory connection object
462 * @param repoName Chart repository name
463 */
464def deleteArtifactoryChartRepo(art, repoName){
465 return restDelete(art, "/repositories/${repoName}")
466}
467
468/**
Oleksandr Hrabar6cd0a642019-07-23 15:13:44 +0300469 * Publish Helm chart to Artifactory
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300470 *
Oleksandr Hrabar6cd0a642019-07-23 15:13:44 +0300471 * @param art Artifactory connection object from artifactory jenkins plugin
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300472 * @param repoName Repository Chart name
Oleksandr Hrabar6cd0a642019-07-23 15:13:44 +0300473 * @param chartPattern Chart pattern for publishing
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300474 */
Oleksandr Hrabar6cd0a642019-07-23 15:13:44 +0300475def publishArtifactoryHelmChart(art, repoName, chartPattern){
476 def uploadSpec = """{
477 "files": [
478 {
479 "pattern": "${chartPattern}",
Vasyl Saienko4d08c442021-01-18 11:44:03 +0200480 "target": "artifactory/${repoName}/"
Oleksandr Hrabar6cd0a642019-07-23 15:13:44 +0300481 }
482 ]
483 }"""
484 art.upload spec: uploadSpec
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300485}
486
487/**
488 * Create Helm repo for Artifactory
489 *
490 * @param art Artifactory connection object
491 * @param repoName Repository Chart name
492 * @param chartName Chart name
493 */
494def deleteArtifactoryHelmChart(art, repoName, chartName){
Oleksandr Hrabar04049342019-07-18 11:05:22 +0000495 return restDelete(art, "/repositories/${repoName}", "${chartName}")
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300496}
Alexandr Lovtsov7cdfcd02019-11-29 21:05:17 +0200497
498/**
499 * Get (recursively) list of all files in repo
500 *
501 * @param art Artifactory connection object
502 * @param repoName Repository name
503 * @param path Folder path
504 *
505 * @return List of paths to files in given repo and folder
506 */
507def getRepoFiles(art, repoName, path = '') {
508 List result = []
509 def response = restGet(art, "/storage/${repoName}/${path}")
510 List children = response.get('children', [])
511 // remove '/' to form more safe-looking path
512 path = path.replaceAll('/$', '')
513 for (child in children) {
514 // remove '/' to form more safe-looking path
515 String childUri = child['uri'].replaceAll('^/', '')
516 if (child['folder'].toBoolean()) {
517 result += getRepoFiles(art, repoName, "${path}/${childUri}")
518 } else {
519 result.add("${path}/${childUri}")
520 }
521 }
522 return result
523}
Andrii Baraniukf87f3b12023-05-03 14:27:36 +0300524
525/**
526 * Get artifactory server object
527 *
528 * @param serverName Artifactory server name
529 */
530def getArtifactoryServer(serverName = ''){
531 if (!serverName) {
532 error ("Artifactory serverName must be specified")
533 }
534 return Artifactory.server(serverName)
535}