blob: d9a9983d36929bea8c3f44c47701e82bf23994c2 [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/**
Oleksandr Hrabarba7a3d22019-07-18 17:36:30 +030088 * Make PUT request using Artifactory REST API for helm charts
89 *
90 * @param art Artifactory connection object
91 * @param uri URI which will be appended to artifactory server base URL
92 * @param data JSON Data to PUT
93 * @param prefix Default prefix "/api" (empty values should be for this way)
94 */
95def restPut2(art, uri, prefix, data = null) {
96 return restCall(art, uri, 'PUT', data, ['Accept': '*/*'], prefix)
97}
98
99/**
Sergey Kolekonovba203982016-12-21 18:32:17 +0400100 * Make DELETE request using Artifactory REST API
101 *
102 * @param art Artifactory connection object
103 * @param uri URI which will be appended to artifactory server base URL
104 */
105def restDelete(art, uri) {
106 return restCall(art, uri, 'DELETE', null, ['Accept': '*/*'])
107}
108
109/**
110 * Make POST request using Artifactory REST API and return parsed JSON
111 *
112 * @param art Artifactory connection object
113 * @param uri URI which will be appended to artifactory server base URL
114 * @param data JSON Data to PUT
115 */
116def restPost(art, uri, data = null) {
117 return restCall(art, uri, 'POST', data, ['Accept': '*/*'])
118}
119
120/**
121 * Query artifacts by properties
122 *
Oleksandr Hrabarba7a3d22019-07-18 17:36:30 +0300123 * @param art Artifactory connection object
Sergey Kolekonovba203982016-12-21 18:32:17 +0400124 * @param properties String or list of properties in key=value format
Oleksandr Hrabarba7a3d22019-07-18 17:36:30 +0300125 * @param repo Optional repository to search in
Sergey Kolekonovba203982016-12-21 18:32:17 +0400126 */
127def findArtifactByProperties(art, properties, repo) {
128 query = parseProperties(properties)
129 if (repo) {
130 query = query + "&repos=${repo}"
131 }
132 res = restGet(art, "/search/prop?${query}")
133 return res.results
134}
135
136/**
137 * Parse properties string or map and return URL-encoded string
138 *
139 * @param properties string or key,value map
140 */
141def parseProperties(properties) {
142 if (properties instanceof String) {
143 return properties
144 } else {
145 props = []
146 for (e in properties) {
147 props.push("${e.key}=${e.value}")
148 }
149 props = props.join('|')
150 return props
151 }
152}
153
154/**
155 * Set single property or list of properties to existing artifact
156 *
157 * @param art Artifactory connection object
158 * @param name Name of artifact
159 * @param version Artifact's version, eg. Docker image tag
160 * @param properties String or list of properties in key=value format
161 * @param recursive Set properties recursively (default false)
162 */
163def setProperty(art, name, version, properties, recursive = 0) {
164 props = parseProperties(properties)
165 restPut(art, "/storage/${art.outRepo}/${name}/${version}?properties=${props}&recursive=${recursive}")
166}
167
168/**
169 * Artifactory connection and context parameters
170 *
171 * @param url Artifactory server URL
172 * @param dockerRegistryBase Base to docker registry
173 * @param dockerRegistrySSL Use https to access docker registry
174 * @param outRepo Output repository name used in context of this
175 * connection
176 * @param credentialsID ID of credentials store entry
Jakub Josef79ecec32017-02-17 14:36:28 +0100177 * @param serverName Artifactory server name (optional)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400178 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100179def connection(url, dockerRegistryBase, dockerRegistrySsl, outRepo, credentialsId = "artifactory", serverName = null) {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400180 params = [
181 "url": url,
182 "credentialsId": credentialsId,
183 "docker": [
184 "base": dockerRegistryBase,
185 "ssl": dockerRegistrySsl
186 ],
187 "outRepo": outRepo,
188 "creds": getCredentials(credentialsId)
189 ]
190
191 if (dockerRegistrySsl ?: false) {
192 params["docker"]["proto"] = "https"
193 } else {
194 params["docker"]["proto"] = "http"
195 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100196
197 if (serverName ?: null) {
198 params['server'] = Artifactory.server(serverName)
199 }
200
Sergey Kolekonovba203982016-12-21 18:32:17 +0400201 params["docker"]["url"] = "${params.docker.proto}://${params.outRepo}.${params.docker.base}"
202
203 return params
204}
205
206/**
207 * Push docker image and set artifact properties
208 *
209 * @param art Artifactory connection object
210 * @param img Docker image object
211 * @param imgName Name of docker image
212 * @param properties Map of additional artifact properties
213 * @param timestamp Build timestamp
214 * @param latest Push latest tag if set to true (default true)
215 */
216def dockerPush(art, img, imgName, properties, timestamp, latest = true) {
217 docker.withRegistry(art.docker.url, art.credentialsId) {
218 img.push()
219 // Also mark latest image
220 img.push("latest")
221 }
222
223 properties["build.number"] = currentBuild.build().environment.BUILD_NUMBER
224 properties["build.name"] = currentBuild.build().environment.JOB_NAME
225 properties["timestamp"] = timestamp
226
227 /* Set artifact properties */
228 setProperty(
229 art,
230 imgName,
231 timestamp,
232 properties
233 )
234
235 // ..and the same for latest
236 if (latest == true) {
237 setProperty(
238 art,
239 imgName,
240 "latest",
241 properties
242 )
243 }
244}
245
246/**
247 * Promote docker image to another environment
248 *
249 * @param art Artifactory connection object
250 * @param imgName Name of docker image
251 * @param tag Tag to promote
252 * @param env Environment (repository suffix) to promote to
253 * @param keep Keep artifact in source repository (copy, default true)
254 * @param latest Push latest tag if set to true (default true)
255 */
256def dockerPromote(art, imgName, tag, env, keep = true, latest = true) {
257 /* XXX: promotion this way doesn't work
258 restPost(art, "/docker/${art.outRepo}/v2/promote", [
259 "targetRepo": "${art.outRepo}-${env}",
260 "dockerRepository": imgName,
261 "tag": tag,
262 "copy": keep ? true : false
263 ])
264 */
265
266 action = keep ? "copy" : "move"
267 restPost(art, "/${action}/${art.outRepo}/${imgName}/${tag}?to=${art.outRepo}-${env}/${imgName}/${tag}")
268 if (latest == true) {
269 dockerUrl = "${art.docker.proto}://${art.outRepo}-${env}.${art.docker.base}"
270 docker.withRegistry(dockerUrl, art.credentialsId) {
271 img = docker.image("${imgName}:$tag")
272 img.pull()
273 img.push("latest")
274 }
275 }
276}
277
278/**
279 * Set offline parameter to repositories
280 *
281 * @param art Artifactory connection object
282 * @param repos List of base repositories
283 * @param suffix Suffix to append to new repository names
284 */
285def setOffline(art, repos, suffix) {
286 for (repo in repos) {
287 repoName = "${repo}-${suffix}"
288 restPost(art, "/repositories/${repoName}", ['offline': true])
289 }
290 return
291}
292
293/**
294 * Create repositories based on timestamp or other suffix from already
295 * existing repository
296 *
297 * @param art Artifactory connection object
298 * @param repos List of base repositories
299 * @param suffix Suffix to append to new repository names
300 */
301def createRepos(art, repos, suffix) {
302 def created = []
303 for (repo in repos) {
304 repoNewName = "${repo}-${suffix}"
305 repoOrig = restGet(art, "/repositories/${repo}")
306 repoOrig.key = repoNewName
307 repoNew = restPut(art, "/repositories/${repoNewName}", repoOrig)
308 created.push(repoNewName)
309 }
310 return created
311}
312
313/**
314 * Delete repositories based on timestamp or other suffix
315 *
316 * @param art Artifactory connection object
317 * @param repos List of base repositories
318 * @param suffix Suffix to append to new repository names
319 */
320def deleteRepos(art, repos, suffix) {
321 def deleted = []
322 for (repo in repos) {
323 repoName = "${repo}-${suffix}"
324 restDelete(art, "/repositories/${repoName}")
325 deleted.push(repoName)
326 }
327 return deleted
328}
329
Jakub Josef79ecec32017-02-17 14:36:28 +0100330@NonCPS
331def convertProperties(properties) {
332 return properties.collect { k,v -> "$k=$v" }.join(';')
333}
334
Sergey Kolekonovba203982016-12-21 18:32:17 +0400335/**
336 * Upload debian package
337 *
338 * @param art Artifactory connection object
339 * @param file File path
340 * @param properties Map with additional artifact properties
341 * @param timestamp Image tag
342 */
Sergey Kolekonovba203982016-12-21 18:32:17 +0400343
Jakub Josef79ecec32017-02-17 14:36:28 +0100344def uploadDebian(art, file, properties, distribution, component, timestamp) {
345 def arch = file.split('_')[-1].split('\\.')[0]
Sergey Kolekonovba203982016-12-21 18:32:17 +0400346
347 /* Set artifact properties */
348 properties["build.number"] = currentBuild.build().environment.BUILD_NUMBER
349 properties["build.name"] = currentBuild.build().environment.JOB_NAME
350 properties["timestamp"] = timestamp
Jakub Josef79ecec32017-02-17 14:36:28 +0100351
352 properties["deb.distribution"] = distribution
353 properties["deb.component"] = component
354 properties["deb.architecture"] = arch
355 props = convertProperties(properties)
356
357 def uploadSpec = """{
358 "files": [
359 {
360 "pattern": "${file}",
361 "target": "${art.outRepo}",
362 "props": "${props}"
363 }
364 ]
365 }"""
366 art.server.upload(uploadSpec)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400367}
368
369/**
370 * Build step to upload docker image. For use with eg. parallel
371 *
372 * @param art Artifactory connection object
373 * @param img Image name to push
374 * @param properties Map with additional artifact properties
375 * @param timestamp Image tag
376 */
377def uploadDockerImageStep(art, img, properties, timestamp) {
378 return {
379 println "Uploading artifact ${img} into ${art.outRepo}"
380 dockerPush(
381 art,
382 docker.image("${img}:${timestamp}"),
383 img,
384 properties,
385 timestamp
386 )
387 }
388}
389
390/**
391 * Build step to upload package. For use with eg. parallel
392 *
393 * @param art Artifactory connection object
394 * @param file File path
395 * @param properties Map with additional artifact properties
396 * @param timestamp Image tag
397 */
398def uploadPackageStep(art, file, properties, distribution, component, timestamp) {
399 return {
400 uploadDebian(
401 art,
402 file,
403 properties,
404 distribution,
405 component,
406 timestamp
407 )
408 }
409}
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300410
411/**
412 * Get Helm repo for Artifactory
413 *
414 * @param art Artifactory connection object
415 * @param repoName Chart repository name
416 */
417def getArtifactoryProjectByName(art, repoName){
418 return restGet(art, "/repositories/${repoName}")
419}
420
421/**
422 * Get repo by packageType for Artifactory
423 *
424 * @param art Artifactory connection object
425 * @param packageType Repository package type
426 */
427def getArtifactoryProjectByPackageType(art, repoName){
428 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/**
453 * Create Helm repo for Artifactory
454 *
455 * @param art Artifactory connection object
456 * @param repoName Repository Chart name
457 * @param chartName Chart name
458 */
459def publishArtifactoryHelmChart(art, repoName, chartName){
Oleksandr Hrabar2a742f72019-07-19 12:45:56 +0300460 return restPut2(art, "/${repoName}", "/${chartName}")
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300461}
462
463/**
464 * Create Helm repo for Artifactory
465 *
466 * @param art Artifactory connection object
467 * @param repoName Repository Chart name
468 * @param chartName Chart name
469 */
470def deleteArtifactoryHelmChart(art, repoName, chartName){
Oleksandr Hrabar04049342019-07-18 11:05:22 +0000471 return restDelete(art, "/repositories/${repoName}", "${chartName}")
Oleksandr Hrabarab8ff4f2019-07-05 12:22:33 +0300472}