Add promote-vcp-images pipeline
* Support promote policy restriction
* Support few simple self-check and self-verify
- check md5 for upload\donwload
- check for already-existed-release
- check for source and target images are same
* Enable it in release-mcp-versio pipeline
* Misc: re-ident release-mcp-version.groovy
Closes-Bug: PROD-21371 (PROD:21371)
Change-Id: I8fa486997af2655c910f7290251b0a2dbc05d689
diff --git a/promote-vcp-images.groovy b/promote-vcp-images.groovy
new file mode 100644
index 0000000..181eafa
--- /dev/null
+++ b/promote-vcp-images.groovy
@@ -0,0 +1,134 @@
+/**
+ *
+ * Promote VCP(qcow2) images
+ *
+ * Expected parameters:
+ * VCP_IMAGE_LIST - multiline with qcow2 file names
+ * TAG - Target tag of image.Possible are: "nightly|testing|proposed|201X.X.X"
+ * SOURCE_TAG - Initial tag to be tagged with TAG. Will replace SUBS_SOURCE_VCP_IMAGE_TAG in VCP_IMAGE_LIST
+ * UPLOAD_URL - WebDav url with creds, from\to download images
+ *
+ */
+
+def common = new com.mirantis.mk.Common()
+def jenkinsUtils = new com.mirantis.mk.JenkinsUtils()
+
+// Better to chose slave with ssd and fast network to webDav host
+slaveNode = env.SLAVE_NODE ?: 'jsl23.mcp.mirantis.net'
+def job_env = env.getEnvironment().findAll { k, v -> v }
+def verify = job_env.VERIFY_DOWNLOAD ?: true
+
+
+timeout(time: 6, unit: 'HOURS') {
+ node(slaveNode) {
+
+ String description = ''
+ insufficientPermissions = false
+ try {
+ // Pre-run verify
+ // promote is restricted to users in aptly-promote-users LDAP group
+ if (!jenkinsUtils.currentUserInGroups(["mcp-cicd-admins", "aptly-promote-users"])) {
+ insufficientPermissions = true
+ error(String.format("You don't have permissions to make promote from source:%s to target:%s! Only CI/CD and QA team can perform promote.", job_env.SOURCE_TAG, job_env.TAG))
+ }
+ // Check for required opts
+ for (opt in ['UPLOAD_URL', 'SOURCE_TAG', 'TAG', 'VCP_IMAGE_LIST']) {
+ if (!job_env.get(opt, null)) {
+ error("Invalid input params, at least ${opt} param missing")
+ }
+ }
+ def images = job_env.VCP_IMAGE_LIST.trim().tokenize()
+ for (image in images) {
+ if (image.startsWith('#')) {
+ common.warningMsg("Skipping image ${image}")
+ continue
+ }
+ common.infoMsg("Replacing SUBS_SOURCE_VCP_IMAGE_TAG => ${job_env.SOURCE_TAG}")
+ sourceImage = image.replace('SUBS_SOURCE_VCP_IMAGE_TAG', job_env.SOURCE_TAG)
+ targetImage = image.replace('SUBS_SOURCE_VCP_IMAGE_TAG', job_env.TAG)
+
+ // TODO: normalize url's?
+ sourceImageUrl = job_env.UPLOAD_URL + '/' + sourceImage
+ sourceImageMd5Url = job_env.UPLOAD_URL + '/' + sourceImage + '.md5'
+ targetImageUrl = job_env.UPLOAD_URL + '/' + targetImage
+ targetImageMd5Url = job_env.UPLOAD_URL + '/' + targetImage + '.md5'
+
+ common.infoMsg("Attempt to download: ${sourceImage} => ${targetImage}")
+ common.retry(3, 5) {
+ sh(script: "wget --progress=dot:giga --auth-no-challenge -O ${targetImage} ${sourceImageUrl}")
+ }
+ def targetImageMd5 = common.cutOrDie("md5sum ${targetImage} | tee ${targetImage}.md5", 0)
+ if (verify.toBoolean()) {
+ common.infoMsg("Checking md5's ")
+ sh(script: "wget --progress=dot:giga --auth-no-challenge -O ${targetImage}_source_md5 ${sourceImageMd5Url}")
+ def sourceImageMd5 = readFile(file: "${targetImage}_source_md5").tokenize(' ')[0]
+ // Compare downloaded and remote files
+ if (sourceImageMd5 != targetImageMd5) {
+ error("Image ${targetImage} md5sum verify failed!")
+ } else {
+ common.infoMsg("sourceImageMd5: ${sourceImageMd5} == target to upload ImageMd5: ${targetImageMd5}")
+ }
+ // Compare downloaded file, and remote file-to-be-promoted. If same - no sense to promote same file
+ remoteImageMd5Status = sh(script: "wget --progress=dot:giga --auth-no-challenge -O ${targetImage}_expected_target_md5 ${targetImageMd5Url}", returnStatus: true)
+ if (remoteImageMd5Status == '8') {
+ common.infoMsg("target to upload ImageMd5 file not even exist.Continue..")
+ } else {
+ def remoteImageMd5 = readFile(file: "${targetImage}_expected_target_md5").tokenize(' ')[0]
+ if (sourceImageMd5 == remoteImageMd5) {
+ common.infoMsg("sourceImageMd5: ${sourceImageMd5} and target to upload ImageMd5: ${targetImageMd5} are same")
+ common.warningMsg("Skipping to upload: ${targetImage} since it already same")
+ description += "Skipping to upload: ${targetImage} since it already same\n"
+ continue
+ }
+ }
+ common.infoMsg("Check, that we are not going to overwrite released file..")
+ if (['proposed', 'testing', 'nightly'].contains(job_env.TAG)) {
+ common.infoMsg("Uploading to ${job_env.TAG} looks safe..")
+ } else if (['stable'].contains(job_env.TAG)) {
+ common.warningMsg("Uploading to ${job_env.TAG} not safe! But still possible")
+ } else {
+ common.warningMsg("Looks like uploading to new release: ${job_env.TAG}. Checking, that it is not exist yet..")
+ remoteImageStatus = ''
+ remoteImageStatus = sh(script: "wget --auth-no-challenge --spider ${targetImageUrl} 2>/dev/null", returnStatus: true)
+ // wget return code 8 ,if file not exist
+ if (remoteImageStatus != '8') {
+ error("Attempt to overwrite existing release! Target: ${targetImage} already exist!")
+ }
+ }
+ }
+
+ common.infoMsg("Attempt to UPLOAD: ${targetImage} => ${targetImageUrl}")
+ //
+ def uploadImageStatus = ''
+ def uploadImageMd5Status = ''
+ common.retry(3, 5) {
+ uploadImageStatus = sh(script: "curl -f -T ${targetImage} ${job_env.UPLOAD_URL}", returnStatus: true)
+ if (uploadImageStatus != 0) {
+ error("Uploading file: ${targetImage} failed!")
+ }
+ }
+ uploadImageMd5Status = sh(script: "curl -f -T ${targetImage}.md5 ${job_env.UPLOAD_URL}", returnStatus: true)
+ if (uploadImageMd5Status != 0) {
+ error("Uploading file: ${targetImage}.md5 failed!")
+ }
+
+ description += "<a href='http://apt.mirantis.net:8085/images/${targetImage}'>${job_env.SOURCE_TAG}=>${targetImage}</a>"
+ }
+ currentBuild.description = description
+ } catch (Throwable e) {
+ // If there was an error or exception thrown, the build failed
+ if (insufficientPermissions) {
+ currentBuild.result = "ABORTED"
+ currentBuild.description = "Promote aborted due to insufficient permissions"
+ } else {
+ currentBuild.result = "FAILURE"
+ currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
+ }
+ throw e
+ }
+ finally {
+ common.infoMsg("Cleanup..")
+ sh(script: 'find . -mindepth 1 -delete > /dev/null || true')
+ }
+ }
+}
diff --git a/release-mcp-version.groovy b/release-mcp-version.groovy
index b1b3d77..faf891c 100644
--- a/release-mcp-version.groovy
+++ b/release-mcp-version.groovy
@@ -16,92 +16,104 @@
* DOCKER_IMAGES
* GIT_CREDENTIALS
* GIT_REPO_LIST
+ * VCP_IMAGE_LIST - list of images
+ * RELEASE_VCP_IMAGES - boolean
* EMAIL_NOTIFY
* NOTIFY_RECIPIENTS
* NOTIFY_TEXT
*
-*/
+ */
common = new com.mirantis.mk.Common()
git = new com.mirantis.mk.Git()
-def triggerAptlyPromoteJob(aptlyUrl, components, diffOnly, dumpPublish, packages, recreate, source, storages, target){
- build job: "aptly-promote-all-testing-stable", parameters: [
- [$class: 'StringParameterValue', name: 'APTLY_URL', value: aptlyUrl],
- [$class: 'StringParameterValue', name: 'COMPONENTS', value: components],
- [$class: 'BooleanParameterValue', name: 'DIFF_ONLY', value: diffOnly],
- [$class: 'BooleanParameterValue', name: 'DUMP_PUBLISH', value: dumpPublish],
- [$class: 'StringParameterValue', name: 'PACKAGES', value: packages],
- [$class: 'BooleanParameterValue', name: 'RECREATE', value: recreate],
- [$class: 'StringParameterValue', name: 'SOURCE', value: source],
- [$class: 'StringParameterValue', name: 'STORAGES', value: storages],
- [$class: 'StringParameterValue', name: 'TARGET', value: target],
- ]
+def triggerAptlyPromoteJob(aptlyUrl, components, diffOnly, dumpPublish, packages, recreate, source, storages, target) {
+ build job: "aptly-promote-all-testing-stable", parameters: [
+ [$class: 'StringParameterValue', name: 'APTLY_URL', value: aptlyUrl],
+ [$class: 'StringParameterValue', name: 'COMPONENTS', value: components],
+ [$class: 'BooleanParameterValue', name: 'DIFF_ONLY', value: diffOnly],
+ [$class: 'BooleanParameterValue', name: 'DUMP_PUBLISH', value: dumpPublish],
+ [$class: 'StringParameterValue', name: 'PACKAGES', value: packages],
+ [$class: 'BooleanParameterValue', name: 'RECREATE', value: recreate],
+ [$class: 'StringParameterValue', name: 'SOURCE', value: source],
+ [$class: 'StringParameterValue', name: 'STORAGES', value: storages],
+ [$class: 'StringParameterValue', name: 'TARGET', value: target],
+ ]
}
def triggerDockerMirrorJob(dockerCredentials, dockerRegistryUrl, targetTag, imageList, sourceImageTag) {
- build job: "docker-images-mirror", parameters: [
- [$class: 'StringParameterValue', name: 'TARGET_REGISTRY_CREDENTIALS_ID', value: dockerCredentials],
- [$class: 'StringParameterValue', name: 'REGISTRY_URL', value: dockerRegistryUrl],
- [$class: 'StringParameterValue', name: 'IMAGE_TAG', value: targetTag],
- [$class: 'StringParameterValue', name: 'IMAGE_LIST', value: imageList],
- [$class: 'StringParameterValue', name: 'SOURCE_IMAGE_TAG', value: sourceImageTag],
- ]
+ build job: "docker-images-mirror", parameters: [
+ [$class: 'StringParameterValue', name: 'TARGET_REGISTRY_CREDENTIALS_ID', value: dockerCredentials],
+ [$class: 'StringParameterValue', name: 'REGISTRY_URL', value: dockerRegistryUrl],
+ [$class: 'StringParameterValue', name: 'IMAGE_TAG', value: targetTag],
+ [$class: 'StringParameterValue', name: 'IMAGE_LIST', value: imageList],
+ [$class: 'StringParameterValue', name: 'SOURCE_IMAGE_TAG', value: sourceImageTag],
+ ]
}
def triggerMirrorRepoJob(snapshotId, snapshotName) {
- build job: "mirror-snapshot-name-all", parameters: [
- [$class: 'StringParameterValue', name: 'SNAPSHOT_NAME', value: snapshotName],
- [$class: 'StringParameterValue', name: 'SNAPSHOT_ID', value: snapshotId],
- ]
+ build job: "mirror-snapshot-name-all", parameters: [
+ [$class: 'StringParameterValue', name: 'SNAPSHOT_NAME', value: snapshotName],
+ [$class: 'StringParameterValue', name: 'SNAPSHOT_ID', value: snapshotId],
+ ]
}
def triggerGitTagJob(gitRepoList, gitCredentials, tag, sourceTag) {
- build job: "tag-git-repos-stable", parameters: [
- [$class: 'StringParameterValue', name: 'GIT_REPO_LIST', value: gitRepoList],
- [$class: 'StringParameterValue', name: 'GIT_CREDENTIALS', value: gitCredentials],
- [$class: 'StringParameterValue', name: 'TAG', value: tag],
- [$class: 'StringParameterValue', name: 'SOURCE_TAG', value: sourceTag],
- ]
+ build job: "tag-git-repos-stable", parameters: [
+ [$class: 'StringParameterValue', name: 'GIT_REPO_LIST', value: gitRepoList],
+ [$class: 'StringParameterValue', name: 'GIT_CREDENTIALS', value: gitCredentials],
+ [$class: 'StringParameterValue', name: 'TAG', value: tag],
+ [$class: 'StringParameterValue', name: 'SOURCE_TAG', value: sourceTag],
+ ]
+}
+
+def triggerPromoteVCPJob(VcpImageList, tag, sourceTag) {
+ build job: "promote-vcp-images-all", parameters: [
+ [$class: 'StringParameterValue', name: 'VCP_IMAGE_LIST', value: VcpImageList],
+ [$class: 'StringParameterValue', name: 'TAG', value: tag],
+ [$class: 'StringParameterValue', name: 'SOURCE_TAG', value: sourceTag]
+ ]
}
timeout(time: 12, unit: 'HOURS') {
- node() {
- try {
- stage("Promote"){
- if(RELEASE_APTLY.toBoolean())
- {
- common.infoMsg("Promoting Aptly")
- triggerAptlyPromoteJob(APTLY_URL, 'all', false, true, 'all', false, "(.*)/${SOURCE_REVISION}", APTLY_STORAGES, "{0}/${TARGET_REVISION}")
- }
+ node() {
+ try {
+ stage("Promote") {
+ if (RELEASE_APTLY.toBoolean()) {
+ common.infoMsg("Promoting Aptly")
+ triggerAptlyPromoteJob(APTLY_URL, 'all', false, true, 'all', false, "(.*)/${SOURCE_REVISION}", APTLY_STORAGES, "{0}/${TARGET_REVISION}")
+ }
- if(RELEASE_DEB_MIRRORS.toBoolean()){
- common.infoMsg("Promoting Debmirrors")
- triggerMirrorRepoJob(SOURCE_REVISION, TARGET_REVISION)
- }
+ if (RELEASE_DEB_MIRRORS.toBoolean()) {
+ common.infoMsg("Promoting Debmirrors")
+ triggerMirrorRepoJob(SOURCE_REVISION, TARGET_REVISION)
+ }
- if(RELEASE_DOCKER.toBoolean())
- {
- common.infoMsg("Promoting Docker images")
- triggerDockerMirrorJob(DOCKER_CREDENTIALS, DOCKER_URL, TARGET_REVISION, DOCKER_IMAGES, SOURCE_REVISION)
- }
+ if (RELEASE_DOCKER.toBoolean()) {
+ common.infoMsg("Promoting Docker images")
+ triggerDockerMirrorJob(DOCKER_CREDENTIALS, DOCKER_URL, TARGET_REVISION, DOCKER_IMAGES, SOURCE_REVISION)
+ }
- if(RELEASE_GIT.toBoolean())
- {
- common.infoMsg("Promoting Git repositories")
- triggerGitTagJob(GIT_REPO_LIST, GIT_CREDENTIALS, TARGET_REVISION, SOURCE_REVISION)
+ if (RELEASE_GIT.toBoolean()) {
+ common.infoMsg("Promoting Git repositories")
+ triggerGitTagJob(GIT_REPO_LIST, GIT_CREDENTIALS, TARGET_REVISION, SOURCE_REVISION)
- }
- if (EMAIL_NOTIFY.toBoolean()) {
- emailext(to: NOTIFY_RECIPIENTS,
- body: NOTIFY_TEXT,
- subject: "MCP Promotion has been done")
- }
- }
- } catch (Throwable e) {
+ }
+ if (RELEASE_VCP_IMAGES.toBoolean()) {
+ common.infoMsg("Promoting VCP images")
+ triggerPromoteVCPJob(VCP_IMAGE_LIST, TARGET_REVISION, SOURCE_REVISION)
+
+ }
+ if (EMAIL_NOTIFY.toBoolean()) {
+ emailext(to: NOTIFY_RECIPIENTS,
+ body: NOTIFY_TEXT,
+ subject: "MCP Promotion has been done")
+ }
+ }
+ } catch (Throwable e) {
// If there was an error or exception thrown, the build failed
currentBuild.result = "FAILURE"
throw e
- }
}
- }
+ }
+}