blob: 494552ee5592158e02ebf330f773980b4a1cefe4 [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
164 */
165def connection(url, dockerRegistryBase, dockerRegistrySsl, outRepo, credentialsId = "artifactory") {
166 params = [
167 "url": url,
168 "credentialsId": credentialsId,
169 "docker": [
170 "base": dockerRegistryBase,
171 "ssl": dockerRegistrySsl
172 ],
173 "outRepo": outRepo,
174 "creds": getCredentials(credentialsId)
175 ]
176
177 if (dockerRegistrySsl ?: false) {
178 params["docker"]["proto"] = "https"
179 } else {
180 params["docker"]["proto"] = "http"
181 }
182 params["docker"]["url"] = "${params.docker.proto}://${params.outRepo}.${params.docker.base}"
183
184 return params
185}
186
187/**
188 * Push docker image and set artifact properties
189 *
190 * @param art Artifactory connection object
191 * @param img Docker image object
192 * @param imgName Name of docker image
193 * @param properties Map of additional artifact properties
194 * @param timestamp Build timestamp
195 * @param latest Push latest tag if set to true (default true)
196 */
197def dockerPush(art, img, imgName, properties, timestamp, latest = true) {
198 docker.withRegistry(art.docker.url, art.credentialsId) {
199 img.push()
200 // Also mark latest image
201 img.push("latest")
202 }
203
204 properties["build.number"] = currentBuild.build().environment.BUILD_NUMBER
205 properties["build.name"] = currentBuild.build().environment.JOB_NAME
206 properties["timestamp"] = timestamp
207
208 /* Set artifact properties */
209 setProperty(
210 art,
211 imgName,
212 timestamp,
213 properties
214 )
215
216 // ..and the same for latest
217 if (latest == true) {
218 setProperty(
219 art,
220 imgName,
221 "latest",
222 properties
223 )
224 }
225}
226
227/**
228 * Promote docker image to another environment
229 *
230 * @param art Artifactory connection object
231 * @param imgName Name of docker image
232 * @param tag Tag to promote
233 * @param env Environment (repository suffix) to promote to
234 * @param keep Keep artifact in source repository (copy, default true)
235 * @param latest Push latest tag if set to true (default true)
236 */
237def dockerPromote(art, imgName, tag, env, keep = true, latest = true) {
238 /* XXX: promotion this way doesn't work
239 restPost(art, "/docker/${art.outRepo}/v2/promote", [
240 "targetRepo": "${art.outRepo}-${env}",
241 "dockerRepository": imgName,
242 "tag": tag,
243 "copy": keep ? true : false
244 ])
245 */
246
247 action = keep ? "copy" : "move"
248 restPost(art, "/${action}/${art.outRepo}/${imgName}/${tag}?to=${art.outRepo}-${env}/${imgName}/${tag}")
249 if (latest == true) {
250 dockerUrl = "${art.docker.proto}://${art.outRepo}-${env}.${art.docker.base}"
251 docker.withRegistry(dockerUrl, art.credentialsId) {
252 img = docker.image("${imgName}:$tag")
253 img.pull()
254 img.push("latest")
255 }
256 }
257}
258
259/**
260 * Set offline parameter to repositories
261 *
262 * @param art Artifactory connection object
263 * @param repos List of base repositories
264 * @param suffix Suffix to append to new repository names
265 */
266def setOffline(art, repos, suffix) {
267 for (repo in repos) {
268 repoName = "${repo}-${suffix}"
269 restPost(art, "/repositories/${repoName}", ['offline': true])
270 }
271 return
272}
273
274/**
275 * Create repositories based on timestamp or other suffix from already
276 * existing repository
277 *
278 * @param art Artifactory connection object
279 * @param repos List of base repositories
280 * @param suffix Suffix to append to new repository names
281 */
282def createRepos(art, repos, suffix) {
283 def created = []
284 for (repo in repos) {
285 repoNewName = "${repo}-${suffix}"
286 repoOrig = restGet(art, "/repositories/${repo}")
287 repoOrig.key = repoNewName
288 repoNew = restPut(art, "/repositories/${repoNewName}", repoOrig)
289 created.push(repoNewName)
290 }
291 return created
292}
293
294/**
295 * Delete repositories based on timestamp or other suffix
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 deleteRepos(art, repos, suffix) {
302 def deleted = []
303 for (repo in repos) {
304 repoName = "${repo}-${suffix}"
305 restDelete(art, "/repositories/${repoName}")
306 deleted.push(repoName)
307 }
308 return deleted
309}
310
311/**
312 * Upload debian package
313 *
314 * @param art Artifactory connection object
315 * @param file File path
316 * @param properties Map with additional artifact properties
317 * @param timestamp Image tag
318 */
319def uploadDebian(art, file, properties, distribution, component, timestamp, data = null) {
320 def fh
321 if (file instanceof java.io.File) {
322 fh = file
323 } else {
324 fh = new File(file)
325 }
326
327 def arch = fh.name.split('_')[-1].split('\\.')[0]
328 if (data) {
329 restPut(art, "/${art.outRepo}/pool/${fh.name};deb.distribution=${distribution};deb.component=${component};deb.architecture=${arch}", data)
330 } else {
331 restPut(art, "/${art.outRepo}/pool/${fh.name};deb.distribution=${distribution};deb.component=${component};deb.architecture=${arch}", fh)
332 }
333
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
338 setProperty(
339 art,
340 "pool/${fh.name}",
341 timestamp,
342 properties
343 )
344}
345
346/**
347 * Build step to upload docker image. For use with eg. parallel
348 *
349 * @param art Artifactory connection object
350 * @param img Image name to push
351 * @param properties Map with additional artifact properties
352 * @param timestamp Image tag
353 */
354def uploadDockerImageStep(art, img, properties, timestamp) {
355 return {
356 println "Uploading artifact ${img} into ${art.outRepo}"
357 dockerPush(
358 art,
359 docker.image("${img}:${timestamp}"),
360 img,
361 properties,
362 timestamp
363 )
364 }
365}
366
367/**
368 * Build step to upload package. For use with eg. parallel
369 *
370 * @param art Artifactory connection object
371 * @param file File path
372 * @param properties Map with additional artifact properties
373 * @param timestamp Image tag
374 */
375def uploadPackageStep(art, file, properties, distribution, component, timestamp) {
376 return {
377 uploadDebian(
378 art,
379 file,
380 properties,
381 distribution,
382 component,
383 timestamp
384 )
385 }
386}