Add mk libraries
Change-Id: I829b299b6329e8f4d4424c89717d432513d1eece
diff --git a/src/com/mirantis/mk/artifactory.groovy b/src/com/mirantis/mk/artifactory.groovy
new file mode 100644
index 0000000..494552e
--- /dev/null
+++ b/src/com/mirantis/mk/artifactory.groovy
@@ -0,0 +1,386 @@
+package com.mirantis.mk
+
+/**
+ *
+ * Artifactory functions
+ *
+ */
+
+/**
+ * Make generic call using Artifactory REST API and return parsed JSON
+ *
+ * @param art Artifactory connection object
+ * @param uri URI which will be appended to artifactory server base URL
+ * @param method HTTP method to use (default GET)
+ * @param data JSON data to POST or PUT
+ * @param headers Map of additional request headers
+ */
+def restCall(art, uri, method = 'GET', data = null, headers = [:]) {
+ def connection = new URL("${art.url}/api${uri}").openConnection()
+ if (method != 'GET') {
+ connection.setRequestMethod(method)
+ }
+
+ connection.setRequestProperty('User-Agent', 'jenkins-groovy')
+ connection.setRequestProperty('Accept', 'application/json')
+ connection.setRequestProperty('Authorization', "Basic " +
+ "${art.creds.username}:${art.creds.password}".bytes.encodeBase64().toString())
+
+ for (header in headers) {
+ connection.setRequestProperty(header.key, header.value)
+ }
+
+ if (data) {
+ connection.setDoOutput(true)
+ if (data instanceof String) {
+ connection.setRequestProperty('Content-Type', 'application/json')
+ dataStr = data
+ } else if (data instanceof java.io.File) {
+ connection.setRequestProperty('Content-Type', 'application/octet-stream')
+ dataStr = data.bytes
+ } else if (data instanceof byte[]) {
+ connection.setRequestProperty('Content-Type', 'application/octet-stream')
+ dataStr = data
+ } else {
+ connection.setRequestProperty('Content-Type', 'application/json')
+ dataStr = new groovy.json.JsonBuilder(data).toString()
+ }
+ def out = new OutputStreamWriter(connection.outputStream)
+ out.write(dataStr)
+ out.close()
+ }
+
+ if ( connection.responseCode >= 200 && connection.responseCode < 300 ) {
+ res = connection.inputStream.text
+ try {
+ return new groovy.json.JsonSlurperClassic().parseText(res)
+ } catch (Exception e) {
+ return res
+ }
+ } else {
+ throw new Exception(connection.responseCode + ": " + connection.inputStream.text)
+ }
+}
+
+/**
+ * Make GET request using Artifactory REST API and return parsed JSON
+ *
+ * @param art Artifactory connection object
+ * @param uri URI which will be appended to artifactory server base URL
+ */
+def restGet(art, uri) {
+ return restCall(art, uri)
+}
+
+/**
+ * Make PUT request using Artifactory REST API and return parsed JSON
+ *
+ * @param art Artifactory connection object
+ * @param uri URI which will be appended to artifactory server base URL
+ * @param data JSON Data to PUT
+ */
+def restPut(art, uri, data = null) {
+ return restCall(art, uri, 'PUT', data, ['Accept': '*/*'])
+}
+
+/**
+ * Make DELETE request using Artifactory REST API
+ *
+ * @param art Artifactory connection object
+ * @param uri URI which will be appended to artifactory server base URL
+ */
+def restDelete(art, uri) {
+ return restCall(art, uri, 'DELETE', null, ['Accept': '*/*'])
+}
+
+/**
+ * Make POST request using Artifactory REST API and return parsed JSON
+ *
+ * @param art Artifactory connection object
+ * @param uri URI which will be appended to artifactory server base URL
+ * @param data JSON Data to PUT
+ */
+def restPost(art, uri, data = null) {
+ return restCall(art, uri, 'POST', data, ['Accept': '*/*'])
+}
+
+/**
+ * Query artifacts by properties
+ *
+ * @param art Artifactory connection object
+ * @param properties String or list of properties in key=value format
+ * @param repo Optional repository to search in
+ */
+def findArtifactByProperties(art, properties, repo) {
+ query = parseProperties(properties)
+ if (repo) {
+ query = query + "&repos=${repo}"
+ }
+ res = restGet(art, "/search/prop?${query}")
+ return res.results
+}
+
+/**
+ * Parse properties string or map and return URL-encoded string
+ *
+ * @param properties string or key,value map
+ */
+def parseProperties(properties) {
+ if (properties instanceof String) {
+ return properties
+ } else {
+ props = []
+ for (e in properties) {
+ props.push("${e.key}=${e.value}")
+ }
+ props = props.join('|')
+ return props
+ }
+}
+
+/**
+ * Set single property or list of properties to existing artifact
+ *
+ * @param art Artifactory connection object
+ * @param name Name of artifact
+ * @param version Artifact's version, eg. Docker image tag
+ * @param properties String or list of properties in key=value format
+ * @param recursive Set properties recursively (default false)
+ */
+def setProperty(art, name, version, properties, recursive = 0) {
+ props = parseProperties(properties)
+ restPut(art, "/storage/${art.outRepo}/${name}/${version}?properties=${props}&recursive=${recursive}")
+}
+
+/**
+ * Artifactory connection and context parameters
+ *
+ * @param url Artifactory server URL
+ * @param dockerRegistryBase Base to docker registry
+ * @param dockerRegistrySSL Use https to access docker registry
+ * @param outRepo Output repository name used in context of this
+ * connection
+ * @param credentialsID ID of credentials store entry
+ */
+def connection(url, dockerRegistryBase, dockerRegistrySsl, outRepo, credentialsId = "artifactory") {
+ params = [
+ "url": url,
+ "credentialsId": credentialsId,
+ "docker": [
+ "base": dockerRegistryBase,
+ "ssl": dockerRegistrySsl
+ ],
+ "outRepo": outRepo,
+ "creds": getCredentials(credentialsId)
+ ]
+
+ if (dockerRegistrySsl ?: false) {
+ params["docker"]["proto"] = "https"
+ } else {
+ params["docker"]["proto"] = "http"
+ }
+ params["docker"]["url"] = "${params.docker.proto}://${params.outRepo}.${params.docker.base}"
+
+ return params
+}
+
+/**
+ * Push docker image and set artifact properties
+ *
+ * @param art Artifactory connection object
+ * @param img Docker image object
+ * @param imgName Name of docker image
+ * @param properties Map of additional artifact properties
+ * @param timestamp Build timestamp
+ * @param latest Push latest tag if set to true (default true)
+ */
+def dockerPush(art, img, imgName, properties, timestamp, latest = true) {
+ docker.withRegistry(art.docker.url, art.credentialsId) {
+ img.push()
+ // Also mark latest image
+ img.push("latest")
+ }
+
+ properties["build.number"] = currentBuild.build().environment.BUILD_NUMBER
+ properties["build.name"] = currentBuild.build().environment.JOB_NAME
+ properties["timestamp"] = timestamp
+
+ /* Set artifact properties */
+ setProperty(
+ art,
+ imgName,
+ timestamp,
+ properties
+ )
+
+ // ..and the same for latest
+ if (latest == true) {
+ setProperty(
+ art,
+ imgName,
+ "latest",
+ properties
+ )
+ }
+}
+
+/**
+ * Promote docker image to another environment
+ *
+ * @param art Artifactory connection object
+ * @param imgName Name of docker image
+ * @param tag Tag to promote
+ * @param env Environment (repository suffix) to promote to
+ * @param keep Keep artifact in source repository (copy, default true)
+ * @param latest Push latest tag if set to true (default true)
+ */
+def dockerPromote(art, imgName, tag, env, keep = true, latest = true) {
+ /* XXX: promotion this way doesn't work
+ restPost(art, "/docker/${art.outRepo}/v2/promote", [
+ "targetRepo": "${art.outRepo}-${env}",
+ "dockerRepository": imgName,
+ "tag": tag,
+ "copy": keep ? true : false
+ ])
+ */
+
+ action = keep ? "copy" : "move"
+ restPost(art, "/${action}/${art.outRepo}/${imgName}/${tag}?to=${art.outRepo}-${env}/${imgName}/${tag}")
+ if (latest == true) {
+ dockerUrl = "${art.docker.proto}://${art.outRepo}-${env}.${art.docker.base}"
+ docker.withRegistry(dockerUrl, art.credentialsId) {
+ img = docker.image("${imgName}:$tag")
+ img.pull()
+ img.push("latest")
+ }
+ }
+}
+
+/**
+ * Set offline parameter to repositories
+ *
+ * @param art Artifactory connection object
+ * @param repos List of base repositories
+ * @param suffix Suffix to append to new repository names
+ */
+def setOffline(art, repos, suffix) {
+ for (repo in repos) {
+ repoName = "${repo}-${suffix}"
+ restPost(art, "/repositories/${repoName}", ['offline': true])
+ }
+ return
+}
+
+/**
+ * Create repositories based on timestamp or other suffix from already
+ * existing repository
+ *
+ * @param art Artifactory connection object
+ * @param repos List of base repositories
+ * @param suffix Suffix to append to new repository names
+ */
+def createRepos(art, repos, suffix) {
+ def created = []
+ for (repo in repos) {
+ repoNewName = "${repo}-${suffix}"
+ repoOrig = restGet(art, "/repositories/${repo}")
+ repoOrig.key = repoNewName
+ repoNew = restPut(art, "/repositories/${repoNewName}", repoOrig)
+ created.push(repoNewName)
+ }
+ return created
+}
+
+/**
+ * Delete repositories based on timestamp or other suffix
+ *
+ * @param art Artifactory connection object
+ * @param repos List of base repositories
+ * @param suffix Suffix to append to new repository names
+ */
+def deleteRepos(art, repos, suffix) {
+ def deleted = []
+ for (repo in repos) {
+ repoName = "${repo}-${suffix}"
+ restDelete(art, "/repositories/${repoName}")
+ deleted.push(repoName)
+ }
+ return deleted
+}
+
+/**
+ * Upload debian package
+ *
+ * @param art Artifactory connection object
+ * @param file File path
+ * @param properties Map with additional artifact properties
+ * @param timestamp Image tag
+ */
+def uploadDebian(art, file, properties, distribution, component, timestamp, data = null) {
+ def fh
+ if (file instanceof java.io.File) {
+ fh = file
+ } else {
+ fh = new File(file)
+ }
+
+ def arch = fh.name.split('_')[-1].split('\\.')[0]
+ if (data) {
+ restPut(art, "/${art.outRepo}/pool/${fh.name};deb.distribution=${distribution};deb.component=${component};deb.architecture=${arch}", data)
+ } else {
+ restPut(art, "/${art.outRepo}/pool/${fh.name};deb.distribution=${distribution};deb.component=${component};deb.architecture=${arch}", fh)
+ }
+
+ /* Set artifact properties */
+ properties["build.number"] = currentBuild.build().environment.BUILD_NUMBER
+ properties["build.name"] = currentBuild.build().environment.JOB_NAME
+ properties["timestamp"] = timestamp
+ setProperty(
+ art,
+ "pool/${fh.name}",
+ timestamp,
+ properties
+ )
+}
+
+/**
+ * Build step to upload docker image. For use with eg. parallel
+ *
+ * @param art Artifactory connection object
+ * @param img Image name to push
+ * @param properties Map with additional artifact properties
+ * @param timestamp Image tag
+ */
+def uploadDockerImageStep(art, img, properties, timestamp) {
+ return {
+ println "Uploading artifact ${img} into ${art.outRepo}"
+ dockerPush(
+ art,
+ docker.image("${img}:${timestamp}"),
+ img,
+ properties,
+ timestamp
+ )
+ }
+}
+
+/**
+ * Build step to upload package. For use with eg. parallel
+ *
+ * @param art Artifactory connection object
+ * @param file File path
+ * @param properties Map with additional artifact properties
+ * @param timestamp Image tag
+ */
+def uploadPackageStep(art, file, properties, distribution, component, timestamp) {
+ return {
+ uploadDebian(
+ art,
+ file,
+ properties,
+ distribution,
+ component,
+ timestamp
+ )
+ }
+}