blob: 3d96d2a0530f5d5cd1b53f26181e3fc561af8484 [file] [log] [blame]
Richard Felkle90ef1c2017-12-08 00:13:56 +01001/**
2 *
3 * Mirror Docker images
4 *
5 * Expected parameters:
6 * TARGET_REGISTRY_CREDENTIALS_ID Credentials for target Docker Registry
7 * TARGET_REGISTRY Target Docker Registry name
8 * REGISTRY_URL Target Docker Registry URL
9 * IMAGE_TAG Tag to use when pushing images
azvyagintsevd162b912018-07-26 10:52:14 +020010 * SOURCE_IMAGE_TAG Tag to use when pulling images(optional,if SOURCE_IMAGE_TAG has been found)
azvyagintsev388a1bd2018-09-06 12:39:49 +030011 * SET_DEFAULT_ARTIFACTORY_PROPERTIES Add extra props. directly to artifactory,
Richard Felkle90ef1c2017-12-08 00:13:56 +010012 * IMAGE_LIST List of images to mirror
azvyagintsev388a1bd2018-09-06 12:39:49 +030013 * Example: docker.elastic.co/elasticsearch/elasticsearch:5.4.1 docker-prod-local.docker.mirantis.net/mirantis/external/docker.elastic.co/elasticsearch
14 * docker.elastic.co/elasticsearch/elasticsearch:SUBS_SOURCE_IMAGE_TAG docker-prod-local.docker.mirantis.net/mirantis/external/elasticsearch:${IMAGE_TAG}* Will be proceed like:
15 * docker tag docker.elastic.co/elasticsearch/elasticsearch:5.4.1 docker-prod-local.docker.mirantis.net/mirantis/external/docker.elastic.co/elasticsearch/elasticsearch:5.4.1
16 *
Richard Felkle90ef1c2017-12-08 00:13:56 +010017 *
18 */
azvyagintsev388a1bd2018-09-06 12:39:49 +030019import java.util.regex.Pattern
Alexander Evseev89b0da42018-12-10 13:14:04 +000020import groovy.json.JsonSlurper
Richard Felkle90ef1c2017-12-08 00:13:56 +010021
azvyagintsev388a1bd2018-09-06 12:39:49 +030022common = new com.mirantis.mk.Common()
Kirill Mashchenko343973c2018-12-25 15:52:24 +040023jenkinsUtils = new com.mirantis.mk.JenkinsUtils()
azvyagintsev388a1bd2018-09-06 12:39:49 +030024external = false
25externalMarker = '/mirantis/external/'
Richard Felkle90ef1c2017-12-08 00:13:56 +010026
azvyagintsevaa7b43a2019-02-02 10:17:58 +020027// slaveNode = env.SLAVE_NODE ?: 'docker'
28slaveNode = env.SLAVE_NODE ?: 'hardware'
azvyagintsev388a1bd2018-09-06 12:39:49 +030029setDefaultArtifactoryProperties = env.SET_DEFAULT_ARTIFACTORY_PROPERTIES ?: true
30
Kirill Mashchenko343973c2018-12-25 15:52:24 +040031
32
Richard Felkle90ef1c2017-12-08 00:13:56 +010033def getImageName(String image) {
34 def regex = Pattern.compile('(?:.+/)?([^:]+)(?::.+)?')
35 def matcher = regex.matcher(image)
azvyagintsev388a1bd2018-09-06 12:39:49 +030036 if (matcher.find()) {
Richard Felkle90ef1c2017-12-08 00:13:56 +010037 def imageName = matcher.group(1)
38 return imageName
azvyagintsev388a1bd2018-09-06 12:39:49 +030039 } else {
40 error("Wrong format of image name.")
Richard Felkle90ef1c2017-12-08 00:13:56 +010041 }
42}
azvyagintsev388a1bd2018-09-06 12:39:49 +030043
Denis Egorenko343a5712018-12-10 19:08:45 +040044def getImageInfo(String imageName) {
45 String unique_image_id = sh(
Kyrylo Mashchenkoa86912d2018-12-21 08:31:47 +000046 script: "docker inspect --format='{{index .RepoDigests 0}}' '${imageName}'",
47 returnStdout: true,
Denis Egorenko343a5712018-12-10 19:08:45 +040048 ).trim()
49 String imageSha256 = unique_image_id.tokenize(':')[1]
50 common.infoMsg("Docker ${imageName} image sha256 is ${imageSha256}")
Kyrylo Mashchenkoa86912d2018-12-21 08:31:47 +000051 return [ 'id': unique_image_id, 'sha256': imageSha256 ]
Denis Egorenko343a5712018-12-10 19:08:45 +040052}
53
54def imageURL(String registry, String imageName, String sha256) {
55 def ret = new URL("https://${registry}/artifactory/api/search/checksum?sha256=${sha256}").getText()
56 // Most probably, we would get many images, especially for external images. We need to guess
57 // exactly one, which we pushing now
58 def tgtGuessImage = imageName.replace(':', '/').replace(registry, '')
59 ArrayList img_data = new JsonSlurper().parseText(ret)['results']
60 def tgtImgUrl = img_data*.uri.find { it.contains(tgtGuessImage) }
61 if (tgtImgUrl) {
62 return tgtImgUrl
63 } else {
64 error("Can't find image ${imageName} in registry ${registry} with sha256: ${sha256}!")
65 }
66}
67
azvyagintsev388a1bd2018-09-06 12:39:49 +030068timeout(time: 4, unit: 'HOURS') {
69 node(slaveNode) {
Kirill Mashchenko2c67fe62018-12-26 02:18:19 +040070 def user = jenkinsUtils.currentUsername()
Denis Egorenko899ee322018-11-19 17:35:50 +040071 currentBuild.description = "${user}: [${env.SOURCE_IMAGE_TAG} => ${env.IMAGE_TAG}]\n${env.IMAGE_LIST}"
Jakub Josefa63f9862018-01-11 17:58:38 +010072 try {
Kirill Mashchenko343973c2018-12-25 15:52:24 +040073 allowedGroups = ['release-engineering']
Roman Vialovd6b9e832019-02-04 12:46:54 +030074 releaseTags = ['proposed', 'release', '2018', '2019', '2020']
Kirill Mashchenko343973c2018-12-25 15:52:24 +040075 tags = [env.SOURCE_IMAGE_TAG, env.IMAGE_TAG]
76 tagInRelease = tags.any { tag -> releaseTags.any { tag.contains(it) } }
77 if (tagInRelease) {
78 if (!jenkinsUtils.currentUserInGroups(allowedGroups)) {
Kirill Mashchenkodfa2e592019-01-11 11:41:12 +040079 throw new Exception("You - ${user} - don't have permissions to run this job with tags ${tags}!")
Kirill Mashchenko343973c2018-12-25 15:52:24 +040080 } else {
Kirill Mashchenko2c67fe62018-12-26 02:18:19 +040081 echo "User `${user}` belongs to one of groups `${allowedGroups}`. Proceeding..."
Kirill Mashchenko343973c2018-12-25 15:52:24 +040082 }
83 }
azvyagintsev388a1bd2018-09-06 12:39:49 +030084 stage("Mirror Docker Images") {
85
Jakub Josefa63f9862018-01-11 17:58:38 +010086 def images = IMAGE_LIST.tokenize('\n')
azvyagintsev388a1bd2018-09-06 12:39:49 +030087 def imageName, sourceImage, targetRegistryPath, imageArray
88 for (image in images) {
89 if (image.trim().indexOf(' ') == -1) {
90 error("Wrong format of image and target repository input")
Jakub Josefa63f9862018-01-11 17:58:38 +010091 }
92 imageArray = image.trim().tokenize(' ')
azvyagintsev388a1bd2018-09-06 12:39:49 +030093 sourceImage = imageArray[0]
94 if (sourceImage.contains('SUBS_SOURCE_IMAGE_TAG')) {
95 common.warningMsg("Replacing SUBS_SOURCE_IMAGE_TAG => ${env.SOURCE_IMAGE_TAG}")
96 sourceImage = sourceImage.replace('SUBS_SOURCE_IMAGE_TAG', env.SOURCE_IMAGE_TAG)
azvyagintsevd162b912018-07-26 10:52:14 +020097 }
azvyagintsev388a1bd2018-09-06 12:39:49 +030098 targetRegistryPath = imageArray[1]
99 targetRegistry = imageArray[1].split('/')[0]
100 imageName = getImageName(sourceImage)
101 targetImageFull = "${targetRegistryPath}/${imageName}:${env.IMAGE_TAG}"
Denis Egorenko2ee651c2018-11-23 17:41:38 +0400102
103 def mcp_artifactory = new com.mirantis.mcp.MCPArtifactory()
104 if (targetImageFull.contains(externalMarker)) {
105 external = true
106 // check if images exists - raise error, as we don't want to rewrite existing one
107 def imageRepo = targetRegistryPath - targetRegistry
Denis Egorenko1b207d12018-11-26 15:35:21 +0400108 if (mcp_artifactory.imageExists(env.REGISTRY_URL, "${imageRepo}/${imageName}", env.IMAGE_TAG)) {
Denis Egorenko2ee651c2018-11-23 17:41:38 +0400109 error("Image ${targetImageFull} already exists!")
110 }
111 }
112
azvyagintsev388a1bd2018-09-06 12:39:49 +0300113 srcImage = docker.image(sourceImage)
azvyagintsev86ba4a02018-10-03 19:21:28 +0300114 common.retry(3, 5) {
115 srcImage.pull()
116 }
Denis Egorenko343a5712018-12-10 19:08:45 +0400117 source_image_sha256 = getImageInfo(sourceImage)['sha256']
azvyagintsev388a1bd2018-09-06 12:39:49 +0300118 // Use sh-docker call for tag, due magic code in plugin:
119 // https://github.com/jenkinsci/docker-workflow-plugin/blob/docker-workflow-1.17/src/main/resources/org/jenkinsci/plugins/docker/workflow/Docker.groovy#L168-L170
120 sh("docker tag ${srcImage.id} ${targetImageFull}")
121 common.infoMsg("Attempt to push docker image into remote registry: ${env.REGISTRY_URL}")
azvyagintsev86ba4a02018-10-03 19:21:28 +0300122 common.retry(3, 5) {
123 docker.withRegistry(env.REGISTRY_URL, env.TARGET_REGISTRY_CREDENTIALS_ID) {
Alexander Evseev3a37d082018-12-10 12:46:39 +0000124 sh("docker push ${targetImageFull}")
azvyagintsev86ba4a02018-10-03 19:21:28 +0300125 }
Alexander Evseevd70ef002018-10-02 13:01:27 +0200126 }
Denis Egorenkoa0f1b2f2018-11-23 17:24:35 +0000127 def buildTime = new Date().format("yyyyMMdd-HH:mm:ss.SSS", TimeZone.getTimeZone('UTC'))
azvyagintsev388a1bd2018-09-06 12:39:49 +0300128
129 if (setDefaultArtifactoryProperties) {
130 common.infoMsg("Processing artifactory props for : ${targetImageFull}")
131 LinkedHashMap artifactoryProperties = [:]
Denis Egorenko343a5712018-12-10 19:08:45 +0400132 def tgtImageInfo = getImageInfo(targetImageFull)
133 def tgt_image_sha256 = tgtImageInfo['sha256']
134 def unique_image_id = tgtImageInfo['id']
135 def tgtImgUrl = imageURL(targetRegistry, targetImageFull, tgt_image_sha256) - '/manifest.json'
Denis Egorenkoa0f1b2f2018-11-23 17:24:35 +0000136 artifactoryProperties = [
Kyrylo Mashchenkoa86912d2018-12-21 08:31:47 +0000137 'com.mirantis.targetTag' : env.IMAGE_TAG,
138 'com.mirantis.uniqueImageId': unique_image_id,
Denis Egorenkoa0f1b2f2018-11-23 17:24:35 +0000139 ]
140 if (external) {
141 artifactoryProperties << ['com.mirantis.externalImage': external]
142 }
Denis Egorenkoa0f1b2f2018-11-23 17:24:35 +0000143 def historyProperties = []
Denis Egorenko4c9b3c52018-12-24 16:11:54 +0400144 try {
145 def sourceRegistry = sourceImage.split('/')[0]
146 def sourceImgUrl = imageURL(sourceRegistry, sourceImage, source_image_sha256) - '/manifest.json'
147 def existingProps = mcp_artifactory.getPropertiesForArtifact(sourceImgUrl)
148 // check does the source image have already history props
149 if (existingProps) {
150 historyProperties = existingProps.get('com.mirantis.versionHistory', [])
151 }
152 } catch (Exception e) {
153 common.warningMsg("Can't find history for ${sourceImage}.")
Denis Egorenkoa0f1b2f2018-11-23 17:24:35 +0000154 }
155 // %5C - backslash symbol is needed
156 historyProperties.add("${buildTime}%5C=${sourceImage}")
Kyrylo Mashchenkoa86912d2018-12-21 08:31:47 +0000157 artifactoryProperties << [ 'com.mirantis.versionHistory': historyProperties.join(',') ]
Denis Egorenkoa0f1b2f2018-11-23 17:24:35 +0000158 common.infoMsg("artifactoryProperties=> ${artifactoryProperties}")
159 common.retry(3, 5) {
Denis Egorenko01918392018-12-10 12:28:46 +0400160 mcp_artifactory.setProperties(tgtImgUrl, artifactoryProperties)
azvyagintsev388a1bd2018-09-06 12:39:49 +0300161 }
162 }
Richard Felkl7c920d02017-12-11 15:28:18 +0100163 }
Richard Felkle90ef1c2017-12-08 00:13:56 +0100164 }
Jakub Josefa63f9862018-01-11 17:58:38 +0100165 } catch (Throwable e) {
azvyagintsev388a1bd2018-09-06 12:39:49 +0300166 // Stub for future processing
Jakub Josefa63f9862018-01-11 17:58:38 +0100167 currentBuild.result = "FAILURE"
168 throw e
Richard Felkle90ef1c2017-12-08 00:13:56 +0100169 }
Richard Felkle90ef1c2017-12-08 00:13:56 +0100170 }
azvyagintsevd162b912018-07-26 10:52:14 +0200171}