blob: 1b45d1f33426af5238b4d5c86216fe856287c377 [file] [log] [blame]
Jakub Josef6ee6f992017-01-27 16:16:04 +01001import java.util.regex.Pattern
2/**
3 *
Jakub Joseffc2f2412017-02-27 15:14:07 +01004 * OS images build pipeline
Jakub Josef6ee6f992017-01-27 16:16:04 +01005 *
azvyagintsev01785672018-02-27 17:33:14 +02006 * Target build slave node:
7 * target-build-slave: [ qemu ]
8 *
9 * This pipeline require to have slave with such dep's:
10 * ubuntu-pkgs: [ qemu-kvm, libvirt-bin, cloud-image-utils ]
11 *
Jakub Josef6ee6f992017-01-27 16:16:04 +010012 * Expected parameters:
13 * BUILD_OS
14 * BUILD_ONLY
15 * PACKER_DEBUG
16 * PACKER_URL
17 * PACKER_ZIP
18 * PACKER_ZIP_MD5
19 * PACKER_ARGS
20 * UPLOAD_URL
21 * SKIP_UPLOAD
22 * CLEANUP_OLD
23 * CLEANUP_KEEP
24 * PIPELINE_LIBS_URL
25 * PIPELINE_LIBS_BRANCH
26 * PIPELINE_LIBS_CREDENTIALS_ID
Jakub Joseffc2f2412017-02-27 15:14:07 +010027 * GLANCE_UPLOAD
28 * GLANCE_IMG_TYPES
29 * GLANCE_URL
30 * GLANCE_CREDENTIALS_ID
31 * GLANCE_PROJECT
32 * GLANCE_ARGS
33 * OPENSTACK_API_CLIENT
Jiri Broulik4ea221c2018-04-10 13:48:06 +020034 * IMAGE_NAME - Name of the result image.
35 * EXTRA_VARIABLES - list of key:value variables required by template.json
Jakub Josef6ee6f992017-01-27 16:16:04 +010036 */
37
Jakub Josef6ee6f992017-01-27 16:16:04 +010038// Load shared libs
Jakub Joseffc2f2412017-02-27 15:14:07 +010039common = new com.mirantis.mk.Common()
Jiri Broulik2c75a202018-04-10 15:05:38 +020040def date = new Date()
41def dateTime = date.format("ddMMyyyy-HHmmss")
Jakub Josef6ee6f992017-01-27 16:16:04 +010042
Jiri Broulik4ea221c2018-04-10 13:48:06 +020043ArrayList extra_vars = EXTRA_VARIABLES.readLines()
44IMAGE_NAME = IMAGE_NAME + "-" + dateTime
45
Jakub Josef6ee6f992017-01-27 16:16:04 +010046@NonCPS
47def getCleanupImageList(remoteImagesString, imageType, osImage) {
48 def remoteImages = remoteImagesString.tokenize("\n")
49 def imageTypeForRegex = Pattern.quote(imageType)
Jakub Josefc6bcfd72017-02-14 18:14:28 +010050 def osImageForRegex = Pattern.quote(osImage.replaceAll(/\./,"-"))
51 def remoteImagesSameType = remoteImages.findAll { it ->
52 it =~ /${imageTypeForRegex}$/
Jakub Josef6ee6f992017-01-27 16:16:04 +010053 }
Jakub Josef338a1cb2018-01-10 18:50:51 +010054 def imagesToClean = remoteImagesSameType.toSorted().findAll { it ->
Jakub Josefc6bcfd72017-02-14 18:14:28 +010055 it =~ /^${osImageForRegex}-/
Jakub Josef6ee6f992017-01-27 16:16:04 +010056 }
Jakub Josef338a1cb2018-01-10 18:50:51 +010057 // dont cleanup non timestamp images
Richard Felkl014083d2018-03-12 14:58:36 +010058 return imagesToClean.findAll { it ->
Jakub Josef338a1cb2018-01-10 18:50:51 +010059 it =~ /${osImageForRegex}-x(64|32)-\d+${imageTypeForRegex}/
60 }
Filip Pytloun35640b62017-02-23 09:45:34 +010061}
Richard Felklc3053fc2018-03-06 11:15:50 +010062
63timeout(time: 12, unit: 'HOURS') {
64 node('qemu') {
65 // Define global variables
66 def workspace = common.getWorkspace()
67 def buildTypes = BUILD_ONLY.tokenize(" ")
68 def createdImages=[]
69 def uploadedImages=[]
70 def cleanedImages=[]
71
72 checkout scm
73 try {
74 stage("prepare") {
75 if (!fileExists("${workspace}/tmp")) {
76 sh "mkdir -p ${workspace}/tmp"
77 }
78 if (!fileExists("${workspace}/images")) {
79 sh "mkdir ${workspace}/images"
80 }
81 }
82 if (!fileExists("bin")) {
83 common.infoMsg("Downloading packer")
84 sh "mkdir bin"
85 dir("bin") {
86 sh "wget -O ${PACKER_ZIP} ${PACKER_URL}"
87 sh "echo \"${PACKER_ZIP_MD5} ${PACKER_ZIP}\" >> md5sum"
88 sh "md5sum -c --status md5sum"
89 sh "unzip ${PACKER_ZIP}"
90 }
91 }
92 // clean images dir before building
93 sh(script: String.format("rm -rf %s/images/*", BUILD_OS), returnStatus: true)
94 // clean virtualenv is exists
95 sh(script: String.format("rm -rf %s/venv", workspace), returnStatus: true)
96
97 stage("build") {
98 dir(BUILD_OS) {
Jiri Broulik4ea221c2018-04-10 13:48:06 +020099 withEnv(extra_vars + [String.format("PATH=%s:%s/bin", env.PATH, workspace),
Richard Felklc3053fc2018-03-06 11:15:50 +0100100 "PACKER_LOG_PATH=${workspace}/packer.log",
101 "PACKER_LOG=1",
Jiri Broulik4ea221c2018-04-10 13:48:06 +0200102 "IMAGE_NAME=${IMAGE_NAME}",
Richard Felklc3053fc2018-03-06 11:15:50 +0100103 "TMPDIR=${workspace}/tmp"
104 ]) {
105 if (PACKER_DEBUG == 'true') {
106 PACKER_ARGS = "${PACKER_ARGS} -debug"
107 }
108
109 if (fileExists("config-drive/user-data.yaml")) {
110 common.infoMsg("Creating cloud-config drive")
111 if (fileExists("config-drive/cloudata.iso")) {
112 sh "rm -v config-drive/cloudata.iso"
113 }
114 sh "cloud-localds config-drive/cloudata.iso config-drive/user-data.yaml"
115 }
116 sh "packer build -only=${BUILD_ONLY} ${PACKER_ARGS} -parallel=false template.json"
117
118 def packerStatus = sh(script: "grep \"Some builds didn't complete successfully and had errors\" ${PACKER_LOG_PATH}", returnStatus: true)
119 // grep returns 0 if find something
120 if (packerStatus != 0) {
121 if (buildTypes.contains("qemu")) {
122 def imageQemu = sh(script: "find images/ | grep -- '-qemu-' | tail -1", returnStdout: true).trim()
123 if (imageQemu != null && imageQemu != "") {
124 def qemuConvertStatus = sh(script: "qemu-img convert -c -O qcow2 ${imageQemu} ${imageQemu}.qcow2", returnStatus:true)
125 if(qemuConvertStatus == 0){
126 def imageDir = imageQemu.substring(0, imageQemu.lastIndexOf("/") + 1)
127 def imageQemuName = imageQemu.substring(imageQemu.lastIndexOf("/") + 1)
128 def moveResult = sh(script: "mv ${imageQemu}.qcow2 ${imageDir}..", returnStatus: true)
129 if(moveResult == 0){
130 sh "rm -rf ${imageDir}"
131 sh "rm -f ${imageQemu}"
132 createdImages.add(imageQemuName+".qcow2")
133 }
134 }else{
135 throw new Exception("Qemu image convert failed")
136 }
Jiri Broulik0b211672018-04-11 09:35:47 +0200137 } else {
138 throw new Exception("No Qemu images with '-qemu-' in its path")
Richard Felklc3053fc2018-03-06 11:15:50 +0100139 }
140 }
141 if (buildTypes.contains("docker")) {
142 def imageDocker = sh(script: "find images/ | grep -- '-docker-' | grep '.tar\$' | tail -1", returnStdout: true).trim()
143 if (imageDocker != null && imageDocker != "") {
144 def pbZip2Status = sh(script: "pbzip2 ${imageDocker}", returnStatus: true)
145 if(pbZip2Status == 0){
146 sh "rm -f ${imageDocker}"
147 createdImages.add(imageDocker+".bz2")
148 }else{
149 throw new Exception("pbzip2 image convert failed")
150 }
Jiri Broulik0b211672018-04-11 09:35:47 +0200151 } else {
152 throw new Exception("No Docker images with '-docker-' in its path")
Richard Felklc3053fc2018-03-06 11:15:50 +0100153 }
154 }
155
156 } else {
157 throw new Exception("Packer build failed")
158 }
159 }
160 }
161 }
162 stage("upload"){
163 dir(BUILD_OS + "/images") {
164 def images = findFiles(glob: "*.*")
165 def imageBuilds = [:]
166 def openstack = new com.mirantis.mk.Openstack()
167 def openstackEnv = String.format("%s/venv", workspace);
168 def openstackVersion = OPENSTACK_API_CLIENT ? OPENSTACK_API_CLIENT : 'liberty'
Richard Felkl26d43ad2018-03-06 11:28:21 +0100169 def rcFile = openstack.createOpenstackEnv(workspace, GLANCE_URL, GLANCE_CREDENTIALS_ID, GLANCE_PROJECT)
Richard Felklc3053fc2018-03-06 11:15:50 +0100170 def glanceImgTypes = GLANCE_IMG_TYPES.tokenize(" ")
171 openstack.setupOpenstackVirtualenv(openstackEnv, openstackVersion)
172 openstack.runOpenstackCommand("pip install python-glanceclient==1.0.0", rcFile, openstackEnv)
173 for (int i = 0; i < images.size(); i++) {
174 def imageName = images[i].name
175 def imageNameList = imageName.tokenize(".")
176 def imageType = "." + imageNameList[imageNameList.size() - 1]
177 if(imageType.equals(".md5")){
178 continue;
179 }
180
181 imageBuilds["build${i}"]={
182 if (SKIP_UPLOAD != 'true') {
183 sh "md5sum ${imageName} > ${imageName}.md5"
184 common.infoMsg("Uploading image " + imageName)
185 def uploadImageStatus = sh(script: "curl -f -T ${imageName} ${UPLOAD_URL}", returnStatus: true)
186 def uploadMd5Status = sh(script: "curl -f -T ${imageName}.md5 ${UPLOAD_URL}", returnStatus: true)
187 // upload latest
188 def latestImageName = imageName.substring(0, imageName.lastIndexOf("-")) + "-latest" + imageType
189 common.infoMsg("Uploading image ${imageName} as latest")
190 def uploadLatestStatus = sh(script: "curl -f -T ${imageName} ${UPLOAD_URL}${latestImageName}", returnStatus: true)
191 def uploadLatestMd5Status = sh(script: "curl -f -T ${imageName}.md5 ${UPLOAD_URL}${latestImageName}.md5", returnStatus: true)
192 if(uploadLatestStatus != 0 || uploadLatestMd5Status != 0){
193 common.errorMsg("Latest image upload failed")
194 }
195 if (GLANCE_UPLOAD == 'true' && glanceImgTypes.contains(imageType.substring(1))) {
196 def glanceRunArgs = String.format("%s --disk-format %s --container-format bare", GLANCE_ARGS, imageType.substring(1))
197 if (GLANCE_PUBLIC == 'true') {
198 glanceRunArgs += " --visibility public"
199 }
200
201 def imageShortName = imageNameList.get(0)
202 openstack.runOpenstackCommand(String.format("glance image-create --name '%s' %s --file %s", imageShortName, glanceRunArgs, imageName), rcFile, openstackEnv)
203 }
204 if(uploadImageStatus==0 && uploadMd5Status == 0){
205 uploadedImages.add(imageName)
206 sh(String.format("rm -r %s %s.md5",imageName, imageName))
207 createdImages.remove(imageName)
208 }else{
209 throw new Exception("Image upload failed")
210 }
211 }
212 if (CLEANUP_OLD == 'true') {
213 def remoteImages = sh(script: "curl -f -sss ${UPLOAD_URL} | grep -Eo '>.*\\.(qcow2|box|tar\\.bz2)</a>' | sed -e 's,>,,g' -e 's,</a,,g'", returnStdout: true)
214 if (remoteImages != "") {
215 def cleanupImages = getCleanupImageList(remoteImages, imageType, BUILD_OS)
216 def deleteCount = cleanupImages.size() - Integer.parseInt(CLEANUP_KEEP)
217 if (deleteCount > 0) {
218 for (int j = 0; j < deleteCount; j++) {
219 common.infoMsg(String.format("Deleting image %s from aptly", cleanupImages[j]))
220 sh "curl -f -X DELETE ${UPLOAD_URL}" + cleanupImages[j]
221 sh "curl -f -X DELETE ${UPLOAD_URL}" + cleanupImages[j] + ".md5"
222 cleanedImages.add(cleanupImages[j])
223 }
224 }
225 }
226 }
227 }
228 }
229 parallel imageBuilds
230 common.infoMsg(String.format("Uploaded %s images with names %s", uploadedImages.size(), uploadedImages.toString()))
231 common.infoMsg(String.format("Cleaned %s images with names %s", cleanedImages.size(), cleanedImages.toString()))
232 }
233 }
234 } catch (Throwable e) {
235 // If there was an error or exception thrown, the build failed
236 currentBuild.result = "FAILURE"
237 throw e
238 } finally {
239 common.sendNotification(currentBuild.result, "", ["slack"])
240 if (buildTypes.contains("docker")) {
241 withEnv(["PACKER_LOG_PATH=${workspace}/packer.log"]) {
242 sh "docker rmi --force \$(grep \"docker: Image ID:\" ${PACKER_LOG_PATH} | cut -d : -f 6 | head -1 | sed s,\\ ,,g) || true"
243 }
244 }
245 // clean created images if error occured
246 if(!createdImages.isEmpty()){
247 dir(BUILD_OS + "/images"){
248 for(int i=0;i<createdImages.size();i++){
249 sh String.format("rm -f %s",createdImages.get(i))
250 }
251 }
252 }
253 }
254 }
azvyagintsev6d453852018-02-26 16:56:37 +0200255}