blob: d1eb21853c631b590e6d423b06201ec68bb90fcc [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 *
12 * @param art Artifactory connection object
13 * @param uri URI which will be appended to artifactory server base URL
14 * @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
17 */
18def restCall(art, uri, method = 'GET', data = null, headers = [:]) {
19 def connection = new URL("${art.url}/api${uri}").openConnection()
20 if (method != 'GET') {
21 connection.setRequestMethod(method)
22 }
23
24 connection.setRequestProperty('User-Agent', 'jenkins-groovy')
25 connection.setRequestProperty('Accept', 'application/json')
26 connection.setRequestProperty('Authorization', "Basic " +
27 "${art.creds.username}:${art.creds.password}".bytes.encodeBase64().toString())
28
29 for (header in headers) {
30 connection.setRequestProperty(header.key, header.value)
31 }
32
33 if (data) {
34 connection.setDoOutput(true)
35 if (data instanceof String) {
36 connection.setRequestProperty('Content-Type', 'application/json')
37 dataStr = data
38 } else if (data instanceof java.io.File) {
39 connection.setRequestProperty('Content-Type', 'application/octet-stream')
40 dataStr = data.bytes
41 } else if (data instanceof byte[]) {
42 connection.setRequestProperty('Content-Type', 'application/octet-stream')
43 dataStr = data
44 } else {
45 connection.setRequestProperty('Content-Type', 'application/json')
46 dataStr = new groovy.json.JsonBuilder(data).toString()
47 }
48 def out = new OutputStreamWriter(connection.outputStream)
49 out.write(dataStr)
50 out.close()
51 }
52
53 if ( connection.responseCode >= 200 && connection.responseCode < 300 ) {
54 res = connection.inputStream.text
55 try {
56 return new groovy.json.JsonSlurperClassic().parseText(res)
57 } catch (Exception e) {
58 return res
59 }
60 } else {
61 throw new Exception(connection.responseCode + ": " + connection.inputStream.text)
62 }
63}
64
65/**
66 * Make GET request using Artifactory REST API and return parsed JSON
67 *
68 * @param art Artifactory connection object
69 * @param uri URI which will be appended to artifactory server base URL
70 */
71def restGet(art, uri) {
72 return restCall(art, uri)
73}
74
75/**
76 * Make PUT request using Artifactory REST API and return parsed JSON
77 *
78 * @param art Artifactory connection object
79 * @param uri URI which will be appended to artifactory server base URL
80 * @param data JSON Data to PUT
81 */
82def restPut(art, uri, data = null) {
83 return restCall(art, uri, 'PUT', data, ['Accept': '*/*'])
84}
85
86/**
87 * Make DELETE request using Artifactory REST API
88 *
89 * @param art Artifactory connection object
90 * @param uri URI which will be appended to artifactory server base URL
91 */
92def restDelete(art, uri) {
93 return restCall(art, uri, 'DELETE', null, ['Accept': '*/*'])
94}
95
96/**
97 * Make POST request using Artifactory REST API and return parsed JSON
98 *
99 * @param art Artifactory connection object
100 * @param uri URI which will be appended to artifactory server base URL
101 * @param data JSON Data to PUT
102 */
103def restPost(art, uri, data = null) {
104 return restCall(art, uri, 'POST', data, ['Accept': '*/*'])
105}
106
107/**
108 * Query artifacts by properties
109 *
110 * @param art Artifactory connection object
111 * @param properties String or list of properties in key=value format
112 * @param repo Optional repository to search in
113 */
114def findArtifactByProperties(art, properties, repo) {
115 query = parseProperties(properties)
116 if (repo) {
117 query = query + "&repos=${repo}"
118 }
119 res = restGet(art, "/search/prop?${query}")
120 return res.results
121}
122
123/**
124 * Parse properties string or map and return URL-encoded string
125 *
126 * @param properties string or key,value map
127 */
128def parseProperties(properties) {
129 if (properties instanceof String) {
130 return properties
131 } else {
132 props = []
133 for (e in properties) {
134 props.push("${e.key}=${e.value}")
135 }
136 props = props.join('|')
137 return props
138 }
139}
140
141/**
142 * Set single property or list of properties to existing artifact
143 *
144 * @param art Artifactory connection object
145 * @param name Name of artifact
146 * @param version Artifact's version, eg. Docker image tag
147 * @param properties String or list of properties in key=value format
148 * @param recursive Set properties recursively (default false)
149 */
150def setProperty(art, name, version, properties, recursive = 0) {
151 props = parseProperties(properties)
152 restPut(art, "/storage/${art.outRepo}/${name}/${version}?properties=${props}&recursive=${recursive}")
153}
154
155/**
156 * Artifactory connection and context parameters
157 *
158 * @param url Artifactory server URL
159 * @param dockerRegistryBase Base to docker registry
160 * @param dockerRegistrySSL Use https to access docker registry
161 * @param outRepo Output repository name used in context of this
162 * connection
163 * @param credentialsID ID of credentials store entry
Jakub Josef79ecec32017-02-17 14:36:28 +0100164 * @param serverName Artifactory server name (optional)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400165 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100166def connection(url, dockerRegistryBase, dockerRegistrySsl, outRepo, credentialsId = "artifactory", serverName = null) {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400167 params = [
168 "url": url,
169 "credentialsId": credentialsId,
170 "docker": [
171 "base": dockerRegistryBase,
172 "ssl": dockerRegistrySsl
173 ],
174 "outRepo": outRepo,
175 "creds": getCredentials(credentialsId)
176 ]
177
178 if (dockerRegistrySsl ?: false) {
179 params["docker"]["proto"] = "https"
180 } else {
181 params["docker"]["proto"] = "http"
182 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100183
184 if (serverName ?: null) {
185 params['server'] = Artifactory.server(serverName)
186 }
187
Sergey Kolekonovba203982016-12-21 18:32:17 +0400188 params["docker"]["url"] = "${params.docker.proto}://${params.outRepo}.${params.docker.base}"
189
190 return params
191}
192
193/**
194 * Push docker image and set artifact properties
195 *
196 * @param art Artifactory connection object
197 * @param img Docker image object
198 * @param imgName Name of docker image
199 * @param properties Map of additional artifact properties
200 * @param timestamp Build timestamp
201 * @param latest Push latest tag if set to true (default true)
202 */
203def dockerPush(art, img, imgName, properties, timestamp, latest = true) {
204 docker.withRegistry(art.docker.url, art.credentialsId) {
205 img.push()
206 // Also mark latest image
207 img.push("latest")
208 }
209
210 properties["build.number"] = currentBuild.build().environment.BUILD_NUMBER
211 properties["build.name"] = currentBuild.build().environment.JOB_NAME
212 properties["timestamp"] = timestamp
213
214 /* Set artifact properties */
215 setProperty(
216 art,
217 imgName,
218 timestamp,
219 properties
220 )
221
222 // ..and the same for latest
223 if (latest == true) {
224 setProperty(
225 art,
226 imgName,
227 "latest",
228 properties
229 )
230 }
231}
232
233/**
234 * Promote docker image to another environment
235 *
236 * @param art Artifactory connection object
237 * @param imgName Name of docker image
238 * @param tag Tag to promote
239 * @param env Environment (repository suffix) to promote to
240 * @param keep Keep artifact in source repository (copy, default true)
241 * @param latest Push latest tag if set to true (default true)
242 */
243def dockerPromote(art, imgName, tag, env, keep = true, latest = true) {
244 /* XXX: promotion this way doesn't work
245 restPost(art, "/docker/${art.outRepo}/v2/promote", [
246 "targetRepo": "${art.outRepo}-${env}",
247 "dockerRepository": imgName,
248 "tag": tag,
249 "copy": keep ? true : false
250 ])
251 */
252
253 action = keep ? "copy" : "move"
254 restPost(art, "/${action}/${art.outRepo}/${imgName}/${tag}?to=${art.outRepo}-${env}/${imgName}/${tag}")
255 if (latest == true) {
256 dockerUrl = "${art.docker.proto}://${art.outRepo}-${env}.${art.docker.base}"
257 docker.withRegistry(dockerUrl, art.credentialsId) {
258 img = docker.image("${imgName}:$tag")
259 img.pull()
260 img.push("latest")
261 }
262 }
263}
264
265/**
266 * Set offline parameter to repositories
267 *
268 * @param art Artifactory connection object
269 * @param repos List of base repositories
270 * @param suffix Suffix to append to new repository names
271 */
272def setOffline(art, repos, suffix) {
273 for (repo in repos) {
274 repoName = "${repo}-${suffix}"
275 restPost(art, "/repositories/${repoName}", ['offline': true])
276 }
277 return
278}
279
280/**
281 * Create repositories based on timestamp or other suffix from already
282 * existing repository
283 *
284 * @param art Artifactory connection object
285 * @param repos List of base repositories
286 * @param suffix Suffix to append to new repository names
287 */
288def createRepos(art, repos, suffix) {
289 def created = []
290 for (repo in repos) {
291 repoNewName = "${repo}-${suffix}"
292 repoOrig = restGet(art, "/repositories/${repo}")
293 repoOrig.key = repoNewName
294 repoNew = restPut(art, "/repositories/${repoNewName}", repoOrig)
295 created.push(repoNewName)
296 }
297 return created
298}
299
300/**
301 * Delete repositories based on timestamp or other suffix
302 *
303 * @param art Artifactory connection object
304 * @param repos List of base repositories
305 * @param suffix Suffix to append to new repository names
306 */
307def deleteRepos(art, repos, suffix) {
308 def deleted = []
309 for (repo in repos) {
310 repoName = "${repo}-${suffix}"
311 restDelete(art, "/repositories/${repoName}")
312 deleted.push(repoName)
313 }
314 return deleted
315}
316
Jakub Josef79ecec32017-02-17 14:36:28 +0100317@NonCPS
318def convertProperties(properties) {
319 return properties.collect { k,v -> "$k=$v" }.join(';')
320}
321
Sergey Kolekonovba203982016-12-21 18:32:17 +0400322/**
323 * Upload debian package
324 *
325 * @param art Artifactory connection object
326 * @param file File path
327 * @param properties Map with additional artifact properties
328 * @param timestamp Image tag
329 */
Sergey Kolekonovba203982016-12-21 18:32:17 +0400330
Jakub Josef79ecec32017-02-17 14:36:28 +0100331def uploadDebian(art, file, properties, distribution, component, timestamp) {
332 def arch = file.split('_')[-1].split('\\.')[0]
Sergey Kolekonovba203982016-12-21 18:32:17 +0400333
334 /* Set artifact properties */
335 properties["build.number"] = currentBuild.build().environment.BUILD_NUMBER
336 properties["build.name"] = currentBuild.build().environment.JOB_NAME
337 properties["timestamp"] = timestamp
Jakub Josef79ecec32017-02-17 14:36:28 +0100338
339 properties["deb.distribution"] = distribution
340 properties["deb.component"] = component
341 properties["deb.architecture"] = arch
342 props = convertProperties(properties)
343
344 def uploadSpec = """{
345 "files": [
346 {
347 "pattern": "${file}",
348 "target": "${art.outRepo}",
349 "props": "${props}"
350 }
351 ]
352 }"""
353 art.server.upload(uploadSpec)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400354}
355
356/**
357 * Build step to upload docker image. For use with eg. parallel
358 *
359 * @param art Artifactory connection object
360 * @param img Image name to push
361 * @param properties Map with additional artifact properties
362 * @param timestamp Image tag
363 */
364def uploadDockerImageStep(art, img, properties, timestamp) {
365 return {
366 println "Uploading artifact ${img} into ${art.outRepo}"
367 dockerPush(
368 art,
369 docker.image("${img}:${timestamp}"),
370 img,
371 properties,
372 timestamp
373 )
374 }
375}
376
377/**
378 * Build step to upload package. For use with eg. parallel
379 *
380 * @param art Artifactory connection object
381 * @param file File path
382 * @param properties Map with additional artifact properties
383 * @param timestamp Image tag
384 */
385def uploadPackageStep(art, file, properties, distribution, component, timestamp) {
386 return {
387 uploadDebian(
388 art,
389 file,
390 properties,
391 distribution,
392 component,
393 timestamp
394 )
395 }
396}