Initial commit
Add infrastructure jobs for sandbox
Related-PROD: RE-336
Change-Id: I2140d47e3fc360ab05f92175b29b31e69b2ec10b
diff --git a/common/pipelines/codenarc.groovy b/common/pipelines/codenarc.groovy
new file mode 100644
index 0000000..c4ae040
--- /dev/null
+++ b/common/pipelines/codenarc.groovy
@@ -0,0 +1,94 @@
+#!groovy
+
+def main() {
+ String gitUrl = "${env.GERRIT_SCHEME}://${env.GERRIT_HOST}:${env.GERRIT_PORT}/${env.GERRIT_PROJECT}"
+ String gitRef = env.GERRIT_REFSPEC
+
+ stage('SCM checkout') {
+ echo "Checking out git repository from ${gitUrl} @ ${gitRef}"
+
+ checkout \
+ $class: 'GitSCM',
+ branches: [[
+ name: 'FETCH_HEAD'
+ ]],
+ userRemoteConfigs: [[
+ url: gitUrl,
+ refspec: gitRef,
+ credentialsId: env.GIT_CREDENTIALS_ID
+ ]],
+ extensions: [[
+ $class: 'WipeWorkspace'
+ ]]
+ }
+
+ stage('Codenarc') {
+ String corenarcRulesFile = 'codenarcRules.groovy'
+ if (!fileExists(corenarcRulesFile)) {
+ writeFile \
+ file: corenarcRulesFile,
+ text: env.DEFAULT_RULES
+ }
+ sh '''#!/bin/bash -ex
+ codenarc \
+ -maxPriority1Violations=0 \
+ -maxPriority2Violations=0 \
+ -maxPriority3Violations=0 \
+ -excludes='**/codenarcRules.groovy' \
+ -rulesetfiles=file:codenarcRules.groovy \
+ -report=console \
+ -report=html:report.html \
+ | tee report.log
+ if [ "${PIPESTATUS[0]}" != '0' ]; then
+ exit 1
+ fi
+ if grep -q 'Compilation failed' report.log ; then
+ exit 1
+ fi
+ if grep -q 'Error processing' report.log ; then
+ exit 1
+ fi
+ '''
+ }
+
+ stage('Report') {
+ archiveArtifacts \
+ artifacts: 'report.html',
+ allowEmptyArchive: true
+ }
+}
+
+String podTpl = """
+ apiVersion: "v1"
+ kind: "Pod"
+ spec:
+ securityContext:
+ runAsUser: 1000
+ containers:
+ - name: "codenarc"
+ image: "${env.DOCKER_IMAGE}"
+ command:
+ - "cat"
+ securityContext:
+ privileged: false
+ tty: true
+"""
+if (env.K8S_CLUSTER == 'unset') {
+ node(env.NODE_LABEL) {
+ docker.image(env.DOCKER_IMAGE).inside('--entrypoint=""') {
+ main()
+ }
+ }
+} else {
+ podTemplate(
+ cloud: env.K8S_CLUSTER,
+ yaml: podTpl,
+ showRawYaml: false
+ ) {
+ node(POD_LABEL) {
+ container('codenarc') {
+ main()
+ }
+ }
+ }
+}
diff --git a/common/pipelines/shellcheck.groovy b/common/pipelines/shellcheck.groovy
new file mode 100644
index 0000000..66a7d37
--- /dev/null
+++ b/common/pipelines/shellcheck.groovy
@@ -0,0 +1,65 @@
+#!groovy
+
+def main() {
+ String gitUrl = "${env.GERRIT_SCHEME}://${env.GERRIT_HOST}:${env.GERRIT_PORT}/${env.GERRIT_PROJECT}"
+ String gitRef = env.GERRIT_REFSPEC
+
+ stage('SCM checkout') {
+ checkout \
+ $class: 'GitSCM',
+ branches: [[
+ name: 'FETCH_HEAD'
+ ]],
+ userRemoteConfigs: [[
+ url: gitUrl,
+ refspec: gitRef,
+ credentialsId: env.GIT_CREDENTIALS_ID
+ ]],
+ extensions: [[
+ $class: 'WipeWorkspace'
+ ]]
+ }
+
+ stage('Shellcheck') {
+ sh '''#!/bin/bash -ex
+ git show --name-only --diff-filter=AM \
+ | grep -E '.sh$' \
+ | xargs --no-run-if-empty shellcheck -e SC1090,SC2013,SC2154,SC2029
+ '''
+ }
+}
+
+String podTpl = """
+ apiVersion: "v1"
+ kind: "Pod"
+ spec:
+ securityContext:
+ runAsUser: 1000
+ containers:
+ - name: "main"
+ image: "${env.DOCKER_IMAGE}"
+ command:
+ - "cat"
+ securityContext:
+ privileged: false
+ tty: true
+"""
+if (env.K8S_CLUSTER == 'unset') {
+ node(env.NODE_LABEL) {
+ docker.image(env.DOCKER_IMAGE).inside('--entrypoint=""') {
+ main()
+ }
+ }
+} else {
+ podTemplate(
+ cloud: env.K8S_CLUSTER,
+ yaml: podTpl,
+ showRawYaml: false
+ ) {
+ node(POD_LABEL) {
+ container('main') {
+ main()
+ }
+ }
+ }
+}
diff --git a/common/pipelines/test-jenkins-jobs.groovy b/common/pipelines/test-jenkins-jobs.groovy
new file mode 100644
index 0000000..e04f644
--- /dev/null
+++ b/common/pipelines/test-jenkins-jobs.groovy
@@ -0,0 +1,218 @@
+#!groovy
+//import groovy.transform.Field
+
+String getJobs(getJobsCmd) {
+ String result
+ dir("${env.WORKSPACE}/output/${env.CI_NAME}") {
+ result = sh \
+ script: """\
+ ${getJobsCmd} \
+ | grep -v '\\/\$' \
+ | grep -E '^(deleting|Files)' \
+ | sed -r 's%^(deleting|Files)\\s%%g' \
+ | sed -r 's%^old/%%' \
+ | cut -d' ' -f1
+ """,
+ returnStdout: true
+ }
+ return result
+}
+
+def main() {
+ // Use gerrit parameters if set with fallback to job param
+ String gitUrl = "${env.GERRIT_SCHEME}://${env.GERRIT_HOST}:${env.GERRIT_PORT}/${env.GERRIT_PROJECT}"
+ String gitRef = env.GERRIT_REFSPEC
+
+ String pathSep = '/'
+
+ String getAddedJobsCmd = 'rsync --dry-run -av --delete old/ new/'
+ String getRemovedJobsCmd = 'rsync --dry-run -av --delete new/ old/'
+ String getDiffJobsCmd = 'diff -rq old/ new/'
+
+ // Set current build description
+ if (env.GERRIT_CHANGE_URL) {
+ currentBuild.description = """
+ <p>
+ Triggered by change: <a href="${env.GERRIT_CHANGE_URL}">${env.GERRIT_CHANGE_NUMBER},${env.GERRIT_PATCHSET_NUMBER}</a><br/>
+ Project: <b>${env.GERRIT_PROJECT}</b><br/>
+ Branch: <b>${env.GERRIT_BRANCH}</b><br/>
+ Subject: <b>${env.GERRIT_CHANGE_SUBJECT}</b><br/>
+ </p>
+ """
+ }
+
+ // Get & prepare source code
+ stage('SCM checkout') {
+ echo "Checking out git repository from ${gitUrl} @ ${gitRef}"
+
+ checkout \
+ $class: 'GitSCM',
+ branches: [
+ [name: 'FETCH_HEAD'],
+ ],
+ userRemoteConfigs: [
+ [url: gitUrl, refspec: gitRef, credentialsId: env.GIT_CREDENTIALS_ID],
+ ],
+ extensions: [
+ [$class: 'WipeWorkspace'],
+ ]
+ }
+
+ stage('Check for non-ascii characters') {
+ def asciiStatus = sh \
+ script: 'grep -q --perl-regexp -R "[^[:ascii:]]" --include \\*.sh --include \\*.yaml --include \\*.groovy *',
+ returnStatus: true
+
+ if (asciiStatus == 0) {
+ error 'Found non-ASCII symbols!!!'
+ }
+ }
+
+ stage('JJB verify') {
+ withEnv(['HOME=/tmp/']) {
+ // Generate current jobs from parent commit (will be used for diff)
+ sh 'tox -v -e compare-xml-new'
+ }
+ }
+
+ stage('JJB compare') {
+ withEnv(['HOME=/tmp/']) {
+ // Generate jobs from parent commit (will be used for diff)
+ sh '''
+ git reset --hard HEAD^
+ git checkout FETCH_HEAD -- tox.ini
+ tox -v -e compare-xml-old
+ '''
+ }
+
+ dir("output/${env.CI_NAME}") {
+ Integer diffStatus = sh \
+ script: 'diff -rq old/ new/',
+ returnStatus: true
+
+ if (diffStatus == 0) {
+ currentBuild.result = 'SUCCESS'
+ currentBuild.description += 'No job changes'
+ currentBuild.getRawBuild().getExecutor().interrupt(Result.SUCCESS)
+ sleep(1) // Interrupt is not blocking and does not take effect immediately.
+ }
+ }
+
+ // Analyse output file and prepare array with results
+
+ String diffJobs = getJobs(getDiffJobsCmd)
+ String addedJobs = getJobs(getAddedJobsCmd)
+ String removedJobs = getJobs(getRemovedJobsCmd)
+
+ // Set job description
+
+ String description = ''
+ String _item, _itemPath
+
+ dir("output/${env.CI_NAME}") {
+ if (diffJobs.size() > 0) {
+ description += '<b>CHANGED</b><ul>'
+ diffJobs.split('\n').each { item ->
+ _item = item.replace('/config.xml', '')
+ try {
+ _itemPath = item.tokenize(pathSep)[0..-2].join(pathSep)
+ } catch (e) {
+ _itemPath = ''
+ }
+ description += "<li><a href=\"${env.BUILD_URL}artifact/output/${env.CI_NAME}/diff/${item}/*view*/\">${_item}</a></li>"
+
+ // Generate diff file
+ sh """
+ mkdir -p diff/${_itemPath}
+ diff -U 50 \
+ 'old/${item}' \
+ 'new/${item}' \
+ > 'diff/${item}' || :
+ """
+ }
+ description += '</ul>'
+ }
+
+ if (addedJobs.size() > 0) {
+ description += '<b>ADDED</b><ul>'
+ addedJobs.split('\n').each { item ->
+ _item = item.replace('/config.xml', '')
+ try {
+ _itemPath = item.tokenize(pathSep)[0..-2].join(pathSep)
+ } catch (e) {
+ _itemPath = ''
+ }
+ description += "<li><a href=\"${env.BUILD_URL}artifact/output/${env.CI_NAME}/diff/${item}/*view*/\">${_item}</a></li>"
+ sh """
+ mkdir -p diff/${_itemPath}
+ cp new/${item} diff/${_itemPath}/
+ """
+ }
+ description += '</ul>'
+ }
+
+ if (removedJobs.size() > 0) {
+ description += '<b>DELETED</b><ul>'
+ removedJobs.split('\n').each { item ->
+ _item = item.replace('/config.xml', '')
+ try {
+ _itemPath = item.tokenize(pathSep)[0..-2].join(pathSep)
+ } catch (e) {
+ _itemPath = ''
+ }
+ description += "<li><a href=\"${env.BUILD_URL}artifact/output/${env.CI_NAME}/diff/${item}/*view*/\">${_item}</a></li>"
+ sh """
+ mkdir -p diff/${_itemPath}
+ cp old/${item} diff/${_itemPath}/
+ """
+ }
+ description += '</ul>'
+ }
+ }
+
+ currentBuild.description += description
+ }
+
+ // Save results
+ stage('Record test results') {
+ archiveArtifacts([
+ artifacts: "output/${env.CI_NAME}/diff/**",
+ allowEmptyArchive: true,
+ ])
+ }
+}
+
+String podTpl = """
+ apiVersion: "v1"
+ kind: "Pod"
+ spec:
+ securityContext:
+ runAsUser: 1000
+ containers:
+ - name: "tox"
+ image: "${env.DOCKER_IMAGE}"
+ command:
+ - "cat"
+ securityContext:
+ privileged: false
+ tty: true
+"""
+if (env.K8S_CLUSTER == 'unset') {
+ node(env.SLAVE_LABEL ?: 'docker') {
+ docker.image(env.DOCKER_IMAGE).inside('--entrypoint=""') {
+ main()
+ }
+ }
+} else {
+ podTemplate(
+ cloud: env.K8S_CLUSTER,
+ yaml: podTpl,
+ showRawYaml: false
+ ) {
+ node(POD_LABEL) {
+ container('tox') {
+ main()
+ }
+ }
+ }
+}
diff --git a/common/pipelines/tox.groovy b/common/pipelines/tox.groovy
new file mode 100644
index 0000000..f0d8ccf
--- /dev/null
+++ b/common/pipelines/tox.groovy
@@ -0,0 +1,67 @@
+#!groovy
+
+def main() {
+ String gitUrl = "${env.GERRIT_SCHEME}://${env.GERRIT_HOST}:${env.GERRIT_PORT}/${env.GERRIT_PROJECT}"
+ String gitRef = env.GERRIT_REFSPEC
+
+ stage('SCM checkout') {
+ echo "Checking out git repository from ${gitUrl} @ ${gitRef}"
+
+ checkout \
+ $class: 'GitSCM',
+ branches: [[
+ name: 'FETCH_HEAD'
+ ]],
+ userRemoteConfigs: [[
+ url: gitUrl,
+ refspec: gitRef,
+ credentialsId: env.GIT_CREDENTIALS_ID
+ ]],
+ extensions: [[
+ $class: 'WipeWorkspace'
+ ]]
+ }
+
+ stage('tox') {
+ sh '''#!/bin/bash -ex
+ tox -v
+ '''
+
+ }
+}
+
+String podTpl = """
+ apiVersion: "v1"
+ kind: "Pod"
+ spec:
+ securityContext:
+ runAsUser: 1000
+ containers:
+ - name: "main"
+ image: "${env.DOCKER_IMAGE}"
+ command:
+ - "cat"
+ securityContext:
+ privileged: false
+ tty: true
+"""
+
+if (env.K8S_CLUSTER == 'unset') {
+ node(env.NODE_LABEL) {
+ docker.image(env.DOCKER_IMAGE).inside('--entrypoint=""') {
+ main()
+ }
+ }
+} else {
+ podTemplate(
+ cloud: env.K8S_CLUSTER,
+ yaml: podTpl,
+ showRawYaml: false
+ ) {
+ node(POD_LABEL) {
+ container('main') {
+ main()
+ }
+ }
+ }
+}
diff --git a/common/pipelines/update-jenkins-config.groovy b/common/pipelines/update-jenkins-config.groovy
new file mode 100644
index 0000000..0e76192
--- /dev/null
+++ b/common/pipelines/update-jenkins-config.groovy
@@ -0,0 +1,75 @@
+#!groovy
+import io.jenkins.plugins.casc.ConfigurationAsCode
+
+@NonCPS
+Boolean testConfig(String cascPath) {
+ def casc = ConfigurationAsCode.get()
+ def form
+ Boolean result = true
+ try {
+ form = casc.doCheckNewSource(cascPath)
+ } catch (Exception e) {
+ result = false
+ message = e.toString()
+ } finally {
+ if (form) {
+ println form.kind
+ println form.message.split('<')[0]
+ if (form.kind.toString() != 'OK') {
+ result = false
+ }
+ }
+ }
+ return result
+}
+
+@NonCPS
+void applyConfig(String cascPath) {
+ ConfigurationAsCode.get().configure(cascPath)
+}
+
+node('master') {
+ Boolean doApply = false
+ String cascPath = env.WORKSPACE
+
+ if (env.GERRIT_EVENT_TYPE == 'ref-updated') {
+ doApply = true
+ cascPath = ConfigurationAsCode.get().getStandardConfig()[0] ?: "${env.HOME}/casc"
+ }
+
+ stage('SCM checkout') {
+ String refSpec = env.GERRIT_REFSPEC ?: env.GERRIT_REFNAME
+ String gitUrl = "ssh://${env.GERRIT_HOST}:${env.GERRIT_PORT}/${env.GERRIT_PROJECT}"
+ echo "Checking out git repository from ${env.GERRIT_PROJECT} @ ${refSpec}"
+
+ dir(cascPath) {
+ checkout([
+ $class: 'GitSCM',
+ branches: [[
+ name: 'FETCH_HEAD',
+ ]],
+ userRemoteConfigs: [[
+ url: gitUrl,
+ refspec: refSpec,
+ credentialsId: env.GIT_CREDENTIALS_ID,
+ ]],
+ extensions: [[
+ $class: 'WipeWorkspace',
+ ]],
+ ])
+ }
+ }
+
+ stage('Test configuration') {
+ if (! testConfig(cascPath) ) {
+ currentBuild.result = 'FAILURE'
+ throw new RuntimeException ('Fail')
+ }
+ }
+
+ if (doApply) {
+ stage('Apply configuration') {
+ applyConfig(cascPath)
+ }
+ }
+}
diff --git a/common/pipelines/update-jenkins-jobs.groovy b/common/pipelines/update-jenkins-jobs.groovy
new file mode 100644
index 0000000..4bb3702
--- /dev/null
+++ b/common/pipelines/update-jenkins-jobs.groovy
@@ -0,0 +1,202 @@
+#!groovy
+
+def main(String cacheHome = 'output/cache') {
+ String gitUrl = "${env.GERRIT_SCHEME}://${env.GERRIT_HOST}:${env.GERRIT_PORT}/${env.GERRIT_PROJECT}"
+ String gitRef = env.GERRIT_BRANCH ?: env.GERRIT_REFNAME
+ String artInfraNamespace = 'binary-dev-local/infra'
+ String artCacheFile = "${env.CI_NAME}.jjb.zip"
+ String artCacheUrl = "${env.ARTIFACTORY_URL}/artifactory/${artInfraNamespace}/${artCacheFile}"
+ String artCredential = env.ART_CREDENTIALS_ID
+ String jenkinsCredential = env.JENKINS_CREDENTIALS_ID
+ def response
+
+ def jenkinsJobs
+ def jjbJobs
+ def jobsToRemove
+
+ currentBuild.description = ''
+
+ // Set current build description
+ if (env.GERRIT_CHANGE_URL) {
+ currentBuild.description = """
+ <p>
+ Triggered by change: <a href="${env.GERRIT_CHANGE_URL}">${env.GERRIT_CHANGE_NUMBER},${env.GERRIT_PATCHSET_NUMBER}</a><br/>
+ Project: <b>${env.GERRIT_PROJECT}</b><br/>
+ Branch: <b>${env.GERRIT_BRANCH}</b><br/>
+ Subject: <b>${env.GERRIT_CHANGE_SUBJECT}</b><br/>
+ </p>
+ """
+ }
+
+ stage('SCM checkout') {
+ if (env.MAINTAIN_MODE.toLowerCase() == 'false') {
+ checkout([
+ $class: 'GitSCM',
+ branches: [[
+ name: 'FETCH_HEAD'
+ ]],
+ userRemoteConfigs: [[
+ url: gitUrl,
+ refspec: gitRef,
+ credentialsId: env.GIT_CREDENTIALS_ID
+ ]],
+ extensions: [[
+ $class: 'WipeWorkspace'
+ ]],
+ ])
+ }
+ }
+
+ stage('Get JJB cache') {
+ dir(cacheHome) {
+ response = httpRequest \
+ url: artCacheUrl,
+ authentication: artCredential,
+ httpMode: 'GET',
+ outputFile: artCacheFile,
+ validResponseCodes: '100:399,404'
+ if (response.status != 404) {
+ unzip \
+ zipFile: artCacheFile,
+ dir: '.cache'
+ }
+ }
+ }
+
+ stage('Update JJB jobs') {
+ if (env.MAINTAIN_MODE.toLowerCase() == 'false') {
+ withCredentials([
+ usernamePassword(
+ credentialsId: env.JENKINS_CREDENTIALS_ID,
+ usernameVariable: 'JJB_USER',
+ passwordVariable: 'JJB_PASSWORD')
+ ]) {
+ withEnv([
+ "HOME=${cacheHome}"
+ ]) {
+ sh 'tox -v -e update $JOBS_LIST'
+ }
+ }
+ } else {
+ input \
+ message: 'Sleeping for maintainance'
+ }
+ }
+
+ stage('Get deployed jobs list') {
+ response = httpRequest \
+ url: "${JENKINS_URL}/crumbIssuer/api/json",
+ authentication: jenkinsCredential
+ def crumb = readJSON text: response.getContent()
+
+ response = httpRequest \
+ url: "${JENKINS_URL}/api/json?tree=jobs[name,description]",
+ authentication: jenkinsCredential,
+ customHeaders: [[
+ name: crumb.crumbRequestField,
+ value: crumb.crumb,
+ maskValue: true
+ ]]
+ def jobs = readJSON text: response.getContent()
+
+ jenkinsJobs = jobs.jobs.findAll {
+ // Filter jenkins jobs deployed by JJB
+ it.description.toString().contains('Managed by Jenkins Job Builder')
+ }.collect{ it.name }
+ }
+
+ stage('Get JJB jobs list') {
+ withEnv([
+ "HOME=${cacheHome}"
+ ]) {
+ sh 'tox -v -e jobs'
+ }
+ dir("output/${env.CI_NAME}") {
+ jjbJobs = sh(
+ script: "grep -rl actions | sed 's|/config.xml\$||g'",
+ returnStdout: true
+ ).trim().readLines()
+ }
+
+ if (jjbJobs.size() == 0) {
+ error 'ERROR: Unexpected JJB output. No generated jobs found'
+ }
+ }
+
+ stage('Remove undefined jobs') {
+ jobsToRemove = jenkinsJobs.findAll { ! jjbJobs.contains(it) }
+
+ if (jobsToRemove.size() > 0) {
+ withCredentials([
+ usernamePassword(
+ credentialsId: jenkinsCredential,
+ usernameVariable: 'JJB_USER',
+ passwordVariable: 'JJB_PASSWORD')
+ ]) {
+ withEnv([
+ "HOME=${cacheHome}"
+ ]) {
+ sh "tox -v -e delete ${jobsToRemove.join(' ')}"
+ }
+ }
+
+ String description = '<b>DELETED</b><ul>'
+ jobsToRemove.each { description += "<li>${it}</li>" }
+ description += '</ul>'
+ currentBuild.description += description
+ } else {
+ currentBuild.description += 'No jobs to remove'
+ }
+ }
+
+ stage('Save JJB cache') {
+ dir(cacheHome) {
+ sh "rm -f ${artCacheFile}"
+ zip \
+ zipFile: artCacheFile,
+ dir: '.cache',
+ glob: 'jenkins_jobs/**'
+ response = httpRequest \
+ url: artCacheUrl,
+ authentication: artCredential,
+ httpMode: 'PUT',
+ multipartName: 'file',
+ uploadFile: artCacheFile
+ }
+ }
+}
+
+String podTpl = """
+ apiVersion: "v1"
+ kind: "Pod"
+ spec:
+ securityContext:
+ runAsUser: 1000
+ containers:
+ - name: "tox"
+ image: "${env.DOCKER_IMAGE}"
+ command:
+ - "cat"
+ securityContext:
+ privileged: false
+ tty: true
+"""
+
+if (env.K8S_CLUSTER == 'unset') {
+ node(env.SLAVE_LABEL ?: 'docker') {
+ main(env.WORKSPACE)
+ }
+} else {
+ podTemplate(
+ cloud: env.K8S_CLUSTER,
+ yaml: podTpl,
+ showRawYaml: false
+ ) {
+ node(POD_LABEL) {
+ container('tox') {
+ main()
+ }
+ }
+ }
+}
+
diff --git a/common/pipelines/yamllint.groovy b/common/pipelines/yamllint.groovy
new file mode 100644
index 0000000..94b618a
--- /dev/null
+++ b/common/pipelines/yamllint.groovy
@@ -0,0 +1,70 @@
+#!groovy
+
+def main() {
+ String gitUrl = "${env.GERRIT_SCHEME}://${env.GERRIT_HOST}:${env.GERRIT_PORT}/${env.GERRIT_PROJECT}"
+ String gitRef = env.GERRIT_REFSPEC
+
+ stage('SCM checkout') {
+ echo "Checking out git repository from ${gitUrl} @ ${gitRef}"
+
+ checkout \
+ $class: 'GitSCM',
+ branches: [[
+ name: 'FETCH_HEAD'
+ ]],
+ userRemoteConfigs: [[
+ url: gitUrl,
+ refspec: gitRef,
+ credentialsId: env.GIT_CREDENTIALS_ID
+ ]],
+ extensions: [[
+ $class: 'WipeWorkspace'
+ ]]
+ }
+
+ stage('Yamllint') {
+ sh '''#!/bin/bash -ex
+ YAMLLINT=$(which yamllint)
+ [ -f '.yamllint' ] && YAMLLINT="${YAMLLINT} -c .yamllint"
+ git diff HEAD^ --name-only --diff-filter=AM \
+ | grep -E '\\.ya?ml$' \
+ | xargs --no-run-if-empty ${YAMLLINT}
+ '''
+
+ }
+}
+
+String podTpl = """
+ apiVersion: "v1"
+ kind: "Pod"
+ spec:
+ securityContext:
+ runAsUser: 1000
+ containers:
+ - name: "yamllint"
+ image: "${env.DOCKER_IMAGE}"
+ command:
+ - "cat"
+ securityContext:
+ privileged: false
+ tty: true
+"""
+if (env.K8S_CLUSTER == 'unset') {
+ node(env.NODE_LABEL) {
+ docker.image(env.DOCKER_IMAGE).inside('--entrypoint=""') {
+ main()
+ }
+ }
+} else {
+ podTemplate(
+ cloud: env.K8S_CLUSTER,
+ yaml: podTpl,
+ showRawYaml: false
+ ) {
+ node(POD_LABEL) {
+ container('yamllint') {
+ main()
+ }
+ }
+ }
+}
diff --git a/common/pvc-for-jjb-update.yaml.k8s b/common/pvc-for-jjb-update.yaml.k8s
new file mode 100644
index 0000000..aebe156
--- /dev/null
+++ b/common/pvc-for-jjb-update.yaml.k8s
@@ -0,0 +1,11 @@
+---
+apiVersion: "v1"
+kind: "PersistentVolumeClaim"
+metadata:
+ name: "jjb-cache"
+spec:
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 2Gi
diff --git a/common/templates/codenarc.yaml b/common/templates/codenarc.yaml
new file mode 100644
index 0000000..4843314
--- /dev/null
+++ b/common/templates/codenarc.yaml
@@ -0,0 +1,37 @@
+---
+
+- job-template:
+ name: infra.codenarc
+ id: common/codenarc
+ project-type: pipeline
+ description: Check groovy scripts by CodeNarc
+ concurrent: True
+
+ properties:
+ - build-discarder:
+ days-to-keep: 14
+ - inject:
+ properties-content: |
+ DOCKER_IMAGE=docker-prod-local.docker.mirantis.net/infra/codenarc:latest
+ K8S_CLUSTER={k8s_cluster}
+ NODE_LABEL={codenarc_node|docker}
+ GIT_CREDENTIALS_ID={git-credentials-id}
+
+
+ parameters:
+ - hidden:
+ name: DEFAULT_RULES
+ default: !include-raw-escape: ../../codenarcRules.groovy
+
+ triggers:
+ - gerrit:
+ server-name: '{gerrit-server}'
+ projects: '{obj:projects}'
+ trigger-on:
+ - patchset-created-event:
+ exclude-drafts: True
+ custom-url: '- ${{JOB_NAME}} ${{BUILD_URL}}artifact/report.html'
+ skip-vote:
+ notbuilt: true
+
+ dsl: !include-raw-escape: ../pipelines/codenarc.groovy
diff --git a/common/templates/shellcheck.yaml b/common/templates/shellcheck.yaml
new file mode 100644
index 0000000..d989d04
--- /dev/null
+++ b/common/templates/shellcheck.yaml
@@ -0,0 +1,31 @@
+---
+- job-template:
+ name: infra.shellcheck
+ id: common/shellcheck
+ project-type: pipeline
+ description: Check shell scripts by ShellCheck
+ concurrent: True
+
+ properties:
+ - build-discarder:
+ days-to-keep: 14
+ - inject:
+ properties-content: |
+ K8S_CLUSTER={k8s_cluster}
+ DOCKER_IMAGE={docker-dev-virtual}/mirantis/openstack-ci/jenkins-job-tests:latest
+ NODE_LABEL={shellcheck_node|docker}
+ GIT_CREDENTIALS_ID={git-credentials-id}
+
+ triggers:
+ - gerrit:
+ server-name: '{gerrit-server}'
+ projects: '{obj:projects}'
+ trigger-on:
+ - patchset-created-event:
+ exclude-drafts: True
+ custom-url: '- ${{JOB_NAME}} ${{BUILD_URL}}console'
+ skip-vote:
+ notbuilt: true
+
+ dsl: !include-raw-escape: ../pipelines/shellcheck.groovy
+
diff --git a/common/templates/tox.yaml b/common/templates/tox.yaml
new file mode 100644
index 0000000..e837ec9
--- /dev/null
+++ b/common/templates/tox.yaml
@@ -0,0 +1,29 @@
+---
+- job-template:
+ name: infra.tox
+ id: common/tox
+ project-type: pipeline
+ description: Run tox tests
+ concurrent: True
+
+ properties:
+ - build-discarder:
+ days-to-keep: 14
+ - inject:
+ properties-content: |
+ K8S_CLUSTER={k8s_cluster}
+ DOCKER_IMAGE={docker-dev-virtual}/mirantis/openstack-ci/jenkins-job-tests:latest
+ NODE_LABEL={tox_node|docker}
+ GIT_CREDENTIALS_ID={git-credentials-id}
+
+ triggers:
+ - gerrit:
+ server-name: '{gerrit-server}'
+ projects: '{obj:projects}'
+ trigger-on:
+ - patchset-created-event:
+ exclude-drafts: True
+ custom-url: '- ${{JOB_NAME}} ${{BUILD_URL}}console'
+ skip-vote: '{obj:skip_vote}'
+
+ dsl: !include-raw-escape: ../pipelines/tox.groovy
diff --git a/common/templates/yamllint.yaml b/common/templates/yamllint.yaml
new file mode 100644
index 0000000..eb5b341
--- /dev/null
+++ b/common/templates/yamllint.yaml
@@ -0,0 +1,29 @@
+---
+- job-template:
+ name: infra.yamllint
+ id: common/yamllint
+ project-type: pipeline
+ description: Check yaml files by yamllint.
+ concurrent: True
+
+ properties:
+ - build-discarder:
+ days-to-keep: 14
+ - inject:
+ properties-content: |
+ K8S_CLUSTER={k8s_cluster}
+ DOCKER_IMAGE={docker-dev-virtual}/mirantis/openstack-ci/jenkins-job-tests:latest
+ NODE_LABEL={yamllint_node|docker}
+ GIT_CREDENTIALS_ID={git-credentials-id}
+
+ triggers:
+ - gerrit:
+ server-name: '{gerrit-server}'
+ projects: '{obj:projects}'
+ trigger-on:
+ - patchset-created-event:
+ exclude-drafts: True
+ custom-url: '- ${{JOB_NAME}} ${{BUILD_URL}}console'
+ skip-vote: '{obj:skip_vote}'
+
+ dsl: !include-raw-escape: ../pipelines/yamllint.groovy
diff --git a/common/test-jenkins-jobs.yaml b/common/test-jenkins-jobs.yaml
new file mode 100644
index 0000000..80f71e8
--- /dev/null
+++ b/common/test-jenkins-jobs.yaml
@@ -0,0 +1,46 @@
+---
+- project:
+ name: test-jenkins-jobs
+ jobs:
+ - infra/jenkins-jobs.check
+
+- job-template:
+ name: infra.jenkins-jobs.check
+ id: infra/jenkins-jobs.check
+ project-type: pipeline
+ description: Check job definitions by Jenkins Job Builder
+ concurrent: True
+
+ properties:
+ - build-discarder:
+ days-to-keep: 14
+ - inject:
+ properties-content: |
+ K8S_CLUSTER={k8s_cluster}
+ GIT_CREDENTIALS_ID={git-credentials-id}
+ DOCKER_IMAGE={docker-dev-virtual}/mirantis/openstack-ci/jenkins-job-tests:latest
+ CI_NAME={ci_name}
+
+ triggers:
+ - gerrit:
+ server-name: '{gerrit-server}'
+ projects:
+ - project-compare-type: PLAIN
+ project-pattern: mcp-ci/jenkins-jobs
+ branches:
+ - branch-pattern: 'master'
+ file-paths:
+ - compare-type: ANT
+ pattern: 'common/*'
+ - compare-type: ANT
+ pattern: 'common/**/*'
+ - compare-type: ANT
+ pattern: 'servers/{ci_name}/*'
+ - compare-type: ANT
+ pattern: 'servers/{ci_name}/**/*'
+ trigger-on:
+ - patchset-created-event:
+ exclude-drafts: True
+ custom-url: '- ${{JOB_NAME}} ${{BUILD_URL}}'
+
+ dsl: !include-raw-escape: pipelines/test-jenkins-jobs.groovy
diff --git a/common/update-jenkins-config.yaml b/common/update-jenkins-config.yaml
new file mode 100644
index 0000000..9be1f85
--- /dev/null
+++ b/common/update-jenkins-config.yaml
@@ -0,0 +1,36 @@
+- project:
+ name: jenkins-config
+ jobs:
+ - infra/jenkins-config.checkupdate
+
+- job-template:
+ name: 'infra.jenkins-config.checkupdate'
+ id: infra/jenkins-config.checkupdate
+ description: |
+ <p>Verify and apply JCasC config</p>
+ project-type: pipeline
+ properties:
+ - build-discarder:
+ days-to-keep: 3
+ - inject:
+ properties-content: |
+ GIT_CREDENTIALS_ID={git-credentials-id}
+
+ concurrent: false
+ triggers:
+ - gerrit:
+ server-name: '{gerrit-server}'
+ trigger-on:
+ - patchset-created-event
+ - ref-updated-event
+ #- comment-added-contains-event:
+ # comment-contains-value: '(?i)^(Patch Set [0-9]+:)?( [\w\\+-]*)*(\n\n)?\s*(rebuild|recheck|retest|reverify)'
+ projects:
+ - project-compare-type: 'PLAIN'
+ project-pattern: 'mcp-ci/jenkins-config'
+ branches:
+ - branch-compare-type: 'PLAIN'
+ branch-pattern: '{jcasc_branch}'
+ custom-url: '* $JOB_NAME $BUILD_URL'
+ dsl: !include-raw-escape: pipelines/update-jenkins-config.groovy
+ sandbox: false
diff --git a/common/update-jenkins-jobs.yaml b/common/update-jenkins-jobs.yaml
new file mode 100644
index 0000000..fdb6a03
--- /dev/null
+++ b/common/update-jenkins-jobs.yaml
@@ -0,0 +1,58 @@
+- project:
+ name: jenkins-jobs
+ jobs:
+ - infra/update-jenkins-jobs
+
+- job-template:
+ name: 'infra.jenkins-jobs.update'
+ id: infra/update-jenkins-jobs
+ project-type: pipeline
+ description: |
+ <p>Update jenkins jobs configuration</p>
+ <p>Requires python-tox package and user credentials stored as JJB_USER and JJB_PASSWORD</p>
+ concurrent: false
+
+ jjb_project: 'mcp-ci/jenkins-jobs'
+
+ properties:
+ - build-discarder:
+ days-to-keep: 3
+ - inject:
+ properties-content: |
+ K8S_CLUSTER={k8s_cluster}
+ GIT_CREDENTIALS_ID={git-credentials-id}
+ JENKINS_CREDENTIALS_ID={jjb_credentials_id}
+ DOCKER_IMAGE={docker-dev-virtual}/mirantis/openstack-ci/jenkins-job-tests:latest
+ CI_NAME={ci_name}
+ SLAVE_LABEL={jjb_update_label}
+ ARTIFACTORY_URL={artifactory-url}
+ ART_CREDENTIALS_ID={artifactory_credentials_id}
+
+ parameters:
+ - bool:
+ name: MAINTAIN_MODE
+ default: false
+ description: Enable maintaining mode
+ - string:
+ name: JOBS_LIST
+ description: |
+ Space separated list of jobs to update. Will update all jobs if empty
+
+ triggers:
+ - gerrit:
+ server-name: '{gerrit-server}'
+ projects:
+ - project-compare-type: PLAIN
+ project-pattern: '{jjb_project}'
+ branches:
+ - branch-pattern: 'master'
+ file-paths:
+ - compare-type: ANT
+ pattern: 'common/**'
+ - compare-type: ANT
+ pattern: 'servers/{ci_name}/**'
+ trigger-on:
+ - change-merged-event
+ custom-url: '- ${{JOB_NAME}} ${{BUILD_URL}}'
+
+ dsl: !include-raw-escape: pipelines/update-jenkins-jobs.groovy