blob: 393dd2e854dbad9cb0bd02a51cd1d87d5da08ad0 [file] [log] [blame]
Tomáš Kukrál7ded3642017-03-27 15:52:51 +02001/**
2 * Generate cookiecutter cluster by individual products
3 *
4 * Expected parameters:
Tomáš Kukrál7ded3642017-03-27 15:52:51 +02005 * COOKIECUTTER_TEMPLATE_CONTEXT Context parameters for the template generation.
Sergey Galkin8b87f6e2018-10-24 18:40:13 +04006 * CREDENTIALS_ID Credentials id for git
azvyagintsev6d678da2018-11-28 21:19:06 +02007 * TEST_MODEL Run syntax tests for model
azvyagintsev3ed704f2018-07-09 15:49:27 +03008 **/
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +00009import static groovy.json.JsonOutput.toJson
10import static groovy.json.JsonOutput.prettyPrint
Denis Egorenko032a2b72019-04-03 14:56:52 +040011import org.apache.commons.net.util.SubnetUtils
Tomáš Kukrál7ded3642017-03-27 15:52:51 +020012
13common = new com.mirantis.mk.Common()
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +000014common2 = new com.mirantis.mcp.Common()
Tomáš Kukrál7ded3642017-03-27 15:52:51 +020015git = new com.mirantis.mk.Git()
16python = new com.mirantis.mk.Python()
chnyda89191012017-05-29 15:38:35 +020017saltModelTesting = new com.mirantis.mk.SaltModelTesting()
azvyagintsev5b7ec892019-05-15 16:37:34 +030018updateSaltFormulasDuringTest = true
Tomáš Kukrál7ded3642017-03-27 15:52:51 +020019
Aleksey Zvyagintsev4a804212019-02-07 13:02:31 +000020slaveNode = env.getProperty('SLAVE_NODE') ?: 'virtual'
azvyagintsev6d678da2018-11-28 21:19:06 +020021gerritCredentials = env.getProperty('CREDENTIALS_ID') ?: 'gerrit'
22runTestModel = (env.getProperty('TEST_MODEL') ?: true).toBoolean()
azvyagintsev866b19a2018-11-20 18:21:43 +020023distribRevision = 'proposed'
24gitGuessedVersion = false
25
Denis Egorenkoce93af32019-05-20 16:57:33 +040026def GenerateModelToxDocker(Map params) {
27 def ccRoot = params['ccRoot']
28 def context = params['context']
29 def outDir = params['outDir']
30 def envOpts = params['envOpts']
31 def tempContextFile = new File(ccRoot, 'tempContext.yaml_' + UUID.randomUUID().toString()).toString()
32 writeFile file: tempContextFile, text: context
33 // Get Jenkins user UID and GID
34 def jenkinsUID = sh(script: 'id -u', returnStdout: true).trim()
35 def jenkinsGID = sh(script: 'id -g', returnStdout: true).trim()
36 /*
37 by default, process in image operates via root user
38 Otherwise, gpg key for model and all files managed by jenkins user
39 To make it compatible, install rrequirementfrom user, but generate model via jenkins
40 for build use upstream Ubuntu Bionic image
41 */
42 def configRun = ['distribRevision': 'nightly',
43 'envOpts' : envOpts + ["CONFIG_FILE=$tempContextFile",
44 "OUTPUT_DIR=${outDir}"
45 ],
Aleksey Zvyagintsev1cbd4d72019-05-24 14:08:19 +000046 'image': 'docker-prod-local.artifactory.mirantis.com/mirantis/cicd/jnlp-slave',
Denis Egorenkoce93af32019-05-20 16:57:33 +040047 'runCommands' : [
48 '001_prepare_generate_auto_reqs': {
Aleksey Zvyagintsev1cbd4d72019-05-24 14:08:19 +000049 sh('''
Denis Egorenkoce93af32019-05-20 16:57:33 +040050 pip install tox
51 ''')
52 },
53 // user & group can be different on host and in docker
Aleksey Zvyagintsev1cbd4d72019-05-24 14:08:19 +000054 '002_set_jenkins_id': {
55 sh("""
Denis Egorenkoce93af32019-05-20 16:57:33 +040056 usermod -u ${jenkinsUID} jenkins
Ivan Berezovskiy6c6118a2019-12-10 17:21:46 +040057 groupmod -g ${jenkinsGID} jenkins
Denis Egorenkoce93af32019-05-20 16:57:33 +040058 """)
59 },
Aleksey Zvyagintsev1cbd4d72019-05-24 14:08:19 +000060 '003_run_generate_auto': {
Denis Egorenkoce93af32019-05-20 16:57:33 +040061 print('[Cookiecutter build] Result:\n' +
62 sh(returnStdout: true, script: 'cd ' + ccRoot + '; su jenkins -c "tox -ve generate_auto" '))
63 }
64 ]
65 ]
66
67 saltModelTesting.setupDockerAndTest(configRun)
68}
69
azvyagintsev866b19a2018-11-20 18:21:43 +020070def globalVariatorsUpdate() {
71 def templateContext = readYaml text: env.COOKIECUTTER_TEMPLATE_CONTEXT
72 def context = templateContext['default_context']
73 // TODO add more check's for critical var's
74 // Since we can't pin to any '_branch' variable from context, to identify 'default git revision' -
75 // because each of them, might be 'refs/' variable, we need to add some tricky trigger of using
76 // 'release/XXX' logic. This is totall guess - so,if even those one failed, to definitely must pass
77 // correct variable finally!
Denis Egorenkof0d213d2019-06-03 19:05:40 +040078 [ context.get('cookiecutter_template_branch'), context.get('shared_reclass_branch'), context.get('mcp_common_scripts_branch') ].any { branch ->
azvyagintsev866b19a2018-11-20 18:21:43 +020079 if (branch.toString().startsWith('release/')) {
80 gitGuessedVersion = branch
81 return true
82 }
83 }
Denis Egorenkof0d213d2019-06-03 19:05:40 +040084
85 [ 'cookiecutter_template_branch', 'shared_reclass_branch', 'mcp_common_scripts_branch' ].each { repoName ->
Denis Egorenko0b283902019-06-04 13:09:15 +040086 if (context['mcp_version'] in [ "nightly", "testing", "stable" ] && ! context.get(repoName)) {
Denis Egorenkof0d213d2019-06-03 19:05:40 +040087 context[repoName] = 'master'
88 } else if (! context.get(repoName)) {
89 context[repoName] = gitGuessedVersion ?: "release/${context['mcp_version']}".toString()
90 }
azvyagintsev866b19a2018-11-20 18:21:43 +020091 }
92 //
93 distribRevision = context['mcp_version']
94 if (['master'].contains(context['mcp_version'])) {
95 distribRevision = 'nightly'
96 }
97 if (distribRevision.contains('/')) {
98 distribRevision = distribRevision.split('/')[-1]
99 }
100 // Check if we are going to test bleeding-edge release, which doesn't have binary release yet
azvyagintsev8e081642019-02-03 19:15:22 +0200101 // After 2018q4 releases, need to also check 'static' repo, for example ubuntu.
azvyagintsev4f34f7a2019-02-26 18:47:37 +0200102 def binTest = common.checkRemoteBinary(['mcp_version': distribRevision])
azvyagintsev8e081642019-02-03 19:15:22 +0200103 if (!binTest.linux_system_repo_url || !binTest.linux_system_repo_ubuntu_url) {
104 common.errorMsg("Binary release: ${distribRevision} not exist or not full. Fallback to 'proposed'! ")
azvyagintsev866b19a2018-11-20 18:21:43 +0200105 distribRevision = 'proposed'
106 }
azvyagintsev8e081642019-02-03 19:15:22 +0200107
azvyagintsev5400d1d2018-12-11 13:19:29 +0200108 // (azvyagintsev) WA for PROD-25732
109 if (context.cookiecutter_template_url.contains('gerrit.mcp.mirantis.com/mk/cookiecutter-templates')) {
110 common.warningMsg('Apply WA for PROD-25732')
111 context.cookiecutter_template_url = 'ssh://gerrit.mcp.mirantis.com:29418/mk/cookiecutter-templates.git'
112 }
Ivan Berezovskiy6c6118a2019-12-10 17:21:46 +0400113 // check, if we are going to test clear release version, w\o any updates and patches.
114 // All 2019.2.X starting from 2019.2.1 and above have update repo which has to be used for salt-formulas.
115 // update/2019.2.0 repo can't be used b/c it points to latest 2019.2.X
116 if (!gitGuessedVersion && (distribRevision == context.mcp_version) && !(distribRevision ==~ /2019\.2\.[1-9]\d*/)) {
azvyagintsev5b7ec892019-05-15 16:37:34 +0300117 updateSaltFormulasDuringTest = false
118 }
119
Ivan Berezovskiy1e66f502019-05-24 18:11:17 +0400120 if (gitGuessedVersion == 'release/proposed/2019.2.0') {
Denis Egorenko83dfe212019-09-11 16:19:01 +0400121 def mcpSaltRepoUpdateVar = 'deb [arch=amd64] http://mirror.mirantis.com/update/proposed/salt-formulas/xenial xenial main'
122 if (context.get('offline_deployment', 'False').toBoolean()) {
Denis Egorenkoa43c66e2019-09-11 16:40:56 +0400123 mcpSaltRepoUpdateVar = "deb [arch=amd64] http://${context.get('aptly_server_deploy_address')}/update/proposed/salt-formulas/xenial xenial main".toString()
Denis Egorenko83dfe212019-09-11 16:19:01 +0400124 }
Ivan Berezovskiy1e66f502019-05-24 18:11:17 +0400125 // CFG node in 2019.2.X update has to be bootstrapped with update/proposed repository for salt formulas
126 context['cloudinit_master_config'] = context.get('cloudinit_master_config', false) ?: [:]
Denis Egorenko83dfe212019-09-11 16:19:01 +0400127 context['cloudinit_master_config']['MCP_SALT_REPO_UPDATES'] = context['cloudinit_master_config'].get('MCP_SALT_REPO_UPDATES', false) ?: mcpSaltRepoUpdateVar
Ivan Berezovskiy1e66f502019-05-24 18:11:17 +0400128 }
129
Denis Egorenkoc1e1d042019-04-26 08:55:27 +0000130 common.infoMsg("Using context:\n" + context)
Aleksey Zvyagintsev7ca28d22019-02-25 11:05:49 +0000131 print prettyPrint(toJson(context))
azvyagintsev866b19a2018-11-20 18:21:43 +0200132 return context
133
134}
azvyagintsevf252b592018-08-13 18:39:14 +0300135
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000136timeout(time: 1, unit: 'HOURS') {
Aleksey Zvyagintsev7ca28d22019-02-25 11:05:49 +0000137 node(slaveNode) {
azvyagintsev866b19a2018-11-20 18:21:43 +0200138 def context = globalVariatorsUpdate()
azvyagintsev6d678da2018-11-28 21:19:06 +0200139 def RequesterEmail = context.get('email_address', '')
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000140 def templateEnv = "${env.WORKSPACE}/template"
azvyagintsev4f34f7a2019-02-26 18:47:37 +0200141 // modelEnv - this is reclass root, aka /srv/salt/reclass
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000142 def modelEnv = "${env.WORKSPACE}/model"
143 def testEnv = "${env.WORKSPACE}/test"
144 def pipelineEnv = "${env.WORKSPACE}/pipelines"
Tomáš Kukrál9f6260f2017-03-29 23:58:26 +0200145
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000146 try {
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000147 //
148 def cutterEnv = "${env.WORKSPACE}/cutter"
149 def systemEnv = "${modelEnv}/classes/system"
150 def testResult = false
151 def user
152 wrap([$class: 'BuildUser']) {
153 user = env.BUILD_USER_ID
154 }
azvyagintsev6d678da2018-11-28 21:19:06 +0200155 currentBuild.description = "${context['cluster_name']} ${RequesterEmail}"
azvyagintsev866b19a2018-11-20 18:21:43 +0200156
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000157 stage('Download Cookiecutter template') {
158 sh(script: 'find . -mindepth 1 -delete > /dev/null || true')
159 checkout([
160 $class : 'GitSCM',
161 branches : [[name: 'FETCH_HEAD'],],
162 extensions : [[$class: 'RelativeTargetDirectory', relativeTargetDir: templateEnv]],
163 userRemoteConfigs: [[url: context['cookiecutter_template_url'], refspec: context['cookiecutter_template_branch'], credentialsId: gerritCredentials],],
164 ])
165 }
166 stage('Create empty reclass model') {
167 dir(path: modelEnv) {
azvyagintsev4f34f7a2019-02-26 18:47:37 +0200168 sh 'rm -rfv .git; git init'
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000169 sshagent(credentials: [gerritCredentials]) {
170 sh "git submodule add ${context['shared_reclass_url']} 'classes/system'"
171 }
172 }
173 checkout([
174 $class : 'GitSCM',
175 branches : [[name: 'FETCH_HEAD'],],
176 extensions : [[$class: 'RelativeTargetDirectory', relativeTargetDir: systemEnv]],
177 userRemoteConfigs: [[url: context['shared_reclass_url'], refspec: context['shared_reclass_branch'], credentialsId: gerritCredentials],],
178 ])
azvyagintsev4f34f7a2019-02-26 18:47:37 +0200179 git.commitGitChanges(modelEnv, 'Added new shared reclass submodule', "${user}@localhost", "${user}")
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000180 }
181
182 stage('Generate model') {
Denis Egorenkoc1e1d042019-04-26 08:55:27 +0000183 // GNUPGHOME environment variable is required for all gpg commands
184 // and for python.generateModel execution
Denis Egorenkoce93af32019-05-20 16:57:33 +0400185 def envOpts = ["GNUPGHOME=${env.WORKSPACE}/gpghome"]
186 withEnv(envOpts) {
Dmitry Pyzhov089fb4f2018-12-11 16:58:00 +0300187 if (context['secrets_encryption_enabled'] == 'True') {
Denis Egorenkoc1e1d042019-04-26 08:55:27 +0000188 sh "mkdir gpghome; chmod 700 gpghome"
Dmitry Pyzhovb5c74c72018-12-17 22:08:50 +0300189 def secretKeyID = RequesterEmail ?: "salt@${context['cluster_domain']}".toString()
Dmitry Pyzhov089fb4f2018-12-11 16:58:00 +0300190 if (!context.get('secrets_encryption_private_key')) {
191 def batchData = """
Denis Egorenko131de5f2019-05-16 14:25:40 +0400192 %echo Generating a basic OpenPGP key for Salt-Master
193 %no-protection
Dmitry Pyzhov089fb4f2018-12-11 16:58:00 +0300194 Key-Type: 1
195 Key-Length: 4096
196 Expire-Date: 0
197 Name-Real: ${context['salt_master_hostname']}.${context['cluster_domain']}
198 Name-Email: ${secretKeyID}
Denis Egorenko131de5f2019-05-16 14:25:40 +0400199 %commit
200 %echo done
Dmitry Pyzhov089fb4f2018-12-11 16:58:00 +0300201 """.stripIndent()
azvyagintsev7d6d46c2019-02-11 14:25:41 +0200202 writeFile file: 'gpg-batch.txt', text: batchData
Dmitry Pyzhov089fb4f2018-12-11 16:58:00 +0300203 sh "gpg --gen-key --batch < gpg-batch.txt"
204 sh "gpg --export-secret-key -a ${secretKeyID} > gpgkey.asc"
205 } else {
azvyagintsev7d6d46c2019-02-11 14:25:41 +0200206 writeFile file: 'gpgkey.asc', text: context['secrets_encryption_private_key']
Dmitry Pyzhov089fb4f2018-12-11 16:58:00 +0300207 sh "gpg --import gpgkey.asc"
Denis Egorenko131de5f2019-05-16 14:25:40 +0400208 secretKeyID = sh(returnStdout: true, script: 'gpg --list-secret-keys --with-colons | grep -E "^sec" | awk -F: \'{print \$5}\'').trim()
Dmitry Pyzhov089fb4f2018-12-11 16:58:00 +0300209 }
210 context['secrets_encryption_key_id'] = secretKeyID
211 }
Stanislav Riazanovda45ea02018-12-21 16:12:50 +0400212 if (context.get('cfg_failsafe_ssh_public_key')) {
azvyagintsev7d6d46c2019-02-11 14:25:41 +0200213 writeFile file: 'failsafe-ssh-key.pub', text: context['cfg_failsafe_ssh_public_key']
Stanislav Riazanovda45ea02018-12-21 16:12:50 +0400214 }
azvyagintsev4f34f7a2019-02-26 18:47:37 +0200215 if (!fileExists(new File(templateEnv, 'tox.ini').toString())) {
azvyagintsev06dfe402019-03-22 17:58:53 +0200216 reqs = new File(templateEnv, 'requirements.txt').toString()
217 if (fileExists(reqs)) {
218 python.setupVirtualenv(cutterEnv, 'python2', [], reqs)
219 } else {
220 python.setupCookiecutterVirtualenv(cutterEnv)
221 }
azvyagintsev4f34f7a2019-02-26 18:47:37 +0200222 python.generateModel(common2.dumpYAML(['default_context': context]), 'default_context', context['salt_master_hostname'], cutterEnv, modelEnv, templateEnv, false)
223 } else {
224 // tox-based CC generated structure of reclass,from the root. Otherwise for bw compat, modelEnv
225 // still expect only lower lvl of project, aka model/classes/cluster/XXX/. So,lets dump result into
226 // temp dir, and then copy it over initial structure.
227 reclassTempRootDir = sh(script: "mktemp -d -p ${env.WORKSPACE}", returnStdout: true).trim()
Denis Egorenkoce93af32019-05-20 16:57:33 +0400228 GenerateModelToxDocker(['context': common2.dumpYAML(['default_context': context]),
229 'ccRoot' : templateEnv,
230 'outDir' : reclassTempRootDir,
231 'envOpts': envOpts])
azvyagintsev4f34f7a2019-02-26 18:47:37 +0200232 dir(modelEnv) {
233 common.warningMsg('Forming reclass-root structure...')
234 sh("cp -ra ${reclassTempRootDir}/reclass/* .")
235 }
236 }
Dmitry Pyzhov089fb4f2018-12-11 16:58:00 +0300237 git.commitGitChanges(modelEnv, "Create model ${context['cluster_name']}", "${user}@localhost", "${user}")
238 }
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000239 }
240
Denis Egorenkoc1e1d042019-04-26 08:55:27 +0000241 stage("Test") {
azvyagintsev6d678da2018-11-28 21:19:06 +0200242 if (runTestModel) {
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000243 sh("cp -r ${modelEnv} ${testEnv}")
azvyagintsev7d6d46c2019-02-11 14:25:41 +0200244 if (fileExists('gpgkey.asc')) {
245 common.infoMsg('gpgkey.asc found!Copy it into reclass folder for tests..')
246 sh("cp -v gpgkey.asc ${testEnv}/salt_master_pillar.asc")
247 }
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000248 def DockerCName = "${env.JOB_NAME.toLowerCase()}_${env.BUILD_TAG.toLowerCase()}"
azvyagintsev5b7ec892019-05-15 16:37:34 +0300249 common.warningMsg("Attempt to run test against:\n" +
250 "DISTRIB_REVISION from ${distribRevision}\n" +
251 "updateSaltFormulasDuringTest = ${updateSaltFormulasDuringTest}")
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000252 try {
253 def config = [
Denis Egorenko926bc7d2019-02-19 14:27:34 +0400254 'dockerHostname' : "${context['salt_master_hostname']}",
255 'domain' : "${context['cluster_domain']}",
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000256 'reclassEnv' : testEnv,
257 'distribRevision' : distribRevision,
258 'dockerContainerName': DockerCName,
Denis Egorenkod87490e2019-02-26 20:00:41 +0400259 'testContext' : 'salt-model-node',
azvyagintsev5b7ec892019-05-15 16:37:34 +0300260 'dockerExtraOpts' : ['--memory=3g'],
261 'updateSaltFormulas' : updateSaltFormulasDuringTest
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000262 ]
Denis Egorenko19a793d2019-11-18 20:50:59 +0400263 if (gitGuessedVersion == 'release/proposed/2019.2.0') {
264 config['updateSaltFormulasRev'] = 'proposed'
265 }
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000266 testResult = saltModelTesting.testNode(config)
267 common.infoMsg("Test finished: SUCCESS")
268 } catch (Exception ex) {
269 common.warningMsg("Test finished: FAILED")
270 testResult = false
271 }
272 } else {
273 common.warningMsg("Test stage has been skipped!")
274 }
275 }
276 stage("Generate config drives") {
277 // apt package genisoimage is required for this stage
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000278 // download create-config-drive
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000279 def commonScriptsRepoUrl = context['mcp_common_scripts_repo'] ?: 'ssh://gerrit.mcp.mirantis.com:29418/mcp/mcp-common-scripts'
280 checkout([
281 $class : 'GitSCM',
282 branches : [[name: 'FETCH_HEAD'],],
283 extensions : [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'mcp-common-scripts']],
azvyagintsev866b19a2018-11-20 18:21:43 +0200284 userRemoteConfigs: [[url: commonScriptsRepoUrl, refspec: context['mcp_common_scripts_branch'], credentialsId: gerritCredentials],],
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000285 ])
286
Denis Egorenkoaeaa0132019-02-25 16:55:08 +0400287 def outdateGeneration = false
288 if (fileExists('mcp-common-scripts/config-drive/create_config_drive.py')) {
289 sh 'cp mcp-common-scripts/config-drive/create_config_drive.py create-config-drive.py'
290 } else {
291 outdateGeneration = true
292 sh 'cp mcp-common-scripts/config-drive/create_config_drive.sh create-config-drive && chmod +x create-config-drive'
293 }
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000294 sh '[ -f mcp-common-scripts/config-drive/master_config.sh ] && cp mcp-common-scripts/config-drive/master_config.sh user_data || cp mcp-common-scripts/config-drive/master_config.yaml user_data'
295
Denis Egorenkoc1e1d042019-04-26 08:55:27 +0000296 sh "git clone --mirror https://github.com/Mirantis/mk-pipelines.git ${pipelineEnv}/mk-pipelines"
297 sh "git clone --mirror https://github.com/Mirantis/pipeline-library.git ${pipelineEnv}/pipeline-library"
Denis Egorenkoaeaa0132019-02-25 16:55:08 +0400298 args = [
azvyagintsev1385db92019-03-22 16:05:10 +0200299 "--user-data user_data", "--model ${modelEnv}",
Denis Egorenkoaeaa0132019-02-25 16:55:08 +0400300 "--mk-pipelines ${pipelineEnv}/mk-pipelines/", "--pipeline-library ${pipelineEnv}/pipeline-library/"
301 ]
Dmitry Pyzhov089fb4f2018-12-11 16:58:00 +0300302 if (context['secrets_encryption_enabled'] == 'True') {
Denis Egorenkoaeaa0132019-02-25 16:55:08 +0400303 args.add('--gpg-key gpgkey.asc')
Dmitry Pyzhov089fb4f2018-12-11 16:58:00 +0300304 }
Stanislav Riazanovda45ea02018-12-21 16:12:50 +0400305 if (context.get('cfg_failsafe_ssh_public_key')) {
Denis Egorenkoaaeda9b2019-02-28 20:55:59 +0400306 if (outdateGeneration) {
307 args.add('--ssh-key failsafe-ssh-key.pub')
308 } else {
Denis Egorenko85ddf0d2019-03-18 18:11:19 +0400309 if (context.get('cfg_failsafe_user')) {
310 args.add('--ssh-keys failsafe-ssh-key.pub')
311 args.add("--cloud-user-name ${context.get('cfg_failsafe_user')}")
312 }
Denis Egorenkoaaeda9b2019-02-28 20:55:59 +0400313 }
Stanislav Riazanovda45ea02018-12-21 16:12:50 +0400314 }
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000315 // load data from model
316 def smc = [:]
317 smc['SALT_MASTER_MINION_ID'] = "${context['salt_master_hostname']}.${context['cluster_domain']}"
318 smc['SALT_MASTER_DEPLOY_IP'] = context['salt_master_management_address']
Ivan Berezovskiy1e66f502019-05-24 18:11:17 +0400319 if (context.get('cloudinit_master_config', false)) {
320 context['cloudinit_master_config'].each { k, v ->
321 smc[k] = v
322 }
323 }
Denis Egorenkoaeaa0132019-02-25 16:55:08 +0400324 if (outdateGeneration) {
325 smc['DEPLOY_NETWORK_GW'] = context['deploy_network_gateway']
326 smc['DEPLOY_NETWORK_NETMASK'] = context['deploy_network_netmask']
327 if (context.get('deploy_network_mtu')) {
328 smc['DEPLOY_NETWORK_MTU'] = context['deploy_network_mtu']
329 }
330 smc['DNS_SERVERS'] = context['dns_server01']
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000331 }
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000332 smc['MCP_VERSION'] = "${context['mcp_version']}"
333 if (context['local_repositories'] == 'True') {
Denis Egorenko6bfa7552019-02-05 19:09:25 +0400334 def localRepoIP = ''
Denis Egorenkof97aec22019-02-05 14:57:33 +0400335 if (context['mcp_version'] in ['2018.4.0', '2018.8.0', '2018.8.0-milestone1', '2018.11.0']) {
Denis Egorenko6bfa7552019-02-05 19:09:25 +0400336 localRepoIP = context['local_repo_url']
Denis Egorenkof97aec22019-02-05 14:57:33 +0400337 smc['MCP_SALT_REPO_URL'] = "http://${localRepoIP}/ubuntu-xenial"
338 } else {
Denis Egorenko6bfa7552019-02-05 19:09:25 +0400339 localRepoIP = context['aptly_server_deploy_address']
Denis Egorenkof97aec22019-02-05 14:57:33 +0400340 smc['MCP_SALT_REPO_URL'] = "http://${localRepoIP}"
341 }
Denis Egorenko6bfa7552019-02-05 19:09:25 +0400342 smc['MCP_SALT_REPO_KEY'] = "http://${localRepoIP}/public.gpg"
Denis Egorenko5f1c0d92019-09-26 09:03:57 +0000343 smc['PIPELINES_FROM_ISO'] = 'false'
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000344 smc['PIPELINE_REPO_URL'] = "http://${localRepoIP}:8088"
345 smc['LOCAL_REPOS'] = 'true'
346 }
347 if (context['upstream_proxy_enabled'] == 'True') {
348 if (context['upstream_proxy_auth_enabled'] == 'True') {
349 smc['http_proxy'] = 'http://' + context['upstream_proxy_user'] + ':' + context['upstream_proxy_password'] + '@' + context['upstream_proxy_address'] + ':' + context['upstream_proxy_port']
350 smc['https_proxy'] = 'http://' + context['upstream_proxy_user'] + ':' + context['upstream_proxy_password'] + '@' + context['upstream_proxy_address'] + ':' + context['upstream_proxy_port']
351 } else {
352 smc['http_proxy'] = 'http://' + context['upstream_proxy_address'] + ':' + context['upstream_proxy_port']
353 smc['https_proxy'] = 'http://' + context['upstream_proxy_address'] + ':' + context['upstream_proxy_port']
354 }
355 }
356
357 for (i in common.entries(smc)) {
Denis Egorenko815d5022019-07-15 16:01:04 +0400358 sh "sed -i 's,export ${i[0]}=.*,export ${i[0]}=\${${i[0]}:-\"${i[1]}\"},' user_data"
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000359 }
360
Denis Egorenko032a2b72019-04-03 14:56:52 +0400361 // calculate netmask
Denis Egorenko1f131b72019-04-05 14:42:48 +0400362 def deployNetworkSubnet = ''
363 if (context.get('deploy_network_subnet')) {
364 def subnet = new SubnetUtils(context['deploy_network_subnet'])
365 deployNetworkSubnet = subnet.getInfo().getNetmask()
366 } else if (context.get('deploy_network_netmask')) { // case for 2018.4.0
367 deployNetworkSubnet = context['deploy_network_netmask']
368 } else {
369 error('Neither context parameter deploy_network_subnet or deploy_network_netmask should be set!')
370 }
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000371 // create cfg config-drive
Denis Egorenkoaeaa0132019-02-25 16:55:08 +0400372 if (outdateGeneration) {
azvyagintsev06dfe402019-03-22 17:58:53 +0200373 args += ["--hostname ${context['salt_master_hostname']}", "${context['salt_master_hostname']}.${context['cluster_domain']}-config.iso"]
Denis Egorenkoaeaa0132019-02-25 16:55:08 +0400374 sh "./create-config-drive ${args.join(' ')}"
375 } else {
376 args += [
377 "--name ${context['salt_master_hostname']}", "--hostname ${context['salt_master_hostname']}.${context['cluster_domain']}", "--clean-up",
Denis Egorenko032a2b72019-04-03 14:56:52 +0400378 "--ip ${context['salt_master_management_address']}", "--netmask ${deployNetworkSubnet}", "--gateway ${context['deploy_network_gateway']}",
Denis Egorenkoaaeda9b2019-02-28 20:55:59 +0400379 "--dns-nameservers ${context['dns_server01']},${context['dns_server02']}"
Denis Egorenkoaeaa0132019-02-25 16:55:08 +0400380 ]
azvyagintsevc4c047a2019-04-04 17:42:48 +0300381 sh "chmod 0755 create-config-drive.py ; ./create-config-drive.py ${args.join(' ')}"
Denis Egorenkoaeaa0132019-02-25 16:55:08 +0400382 }
Aleksey Zvyagintsevaa235f22019-04-24 12:46:42 +0000383 sh("mkdir output-${context['cluster_name']} && mv ${context['salt_master_hostname']}.${context['cluster_domain']}-config.iso output-${context['cluster_name']}/")
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000384
385 // save cfg iso to artifacts
Aleksey Zvyagintsevaa235f22019-04-24 12:46:42 +0000386 archiveArtifacts artifacts: "output-${context['cluster_name']}/${context['salt_master_hostname']}.${context['cluster_domain']}-config.iso"
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000387
388 if (context['local_repositories'] == 'True') {
389 def aptlyServerHostname = context.aptly_server_hostname
390 sh "[ -f mcp-common-scripts/config-drive/mirror_config.yaml ] && cp mcp-common-scripts/config-drive/mirror_config.yaml mirror_config || cp mcp-common-scripts/config-drive/mirror_config.sh mirror_config"
391
392 def smc_apt = [:]
393 smc_apt['SALT_MASTER_DEPLOY_IP'] = context['salt_master_management_address']
Denis Egorenkoaeaa0132019-02-25 16:55:08 +0400394 if (outdateGeneration) {
395 smc_apt['APTLY_DEPLOY_IP'] = context['aptly_server_deploy_address']
396 smc_apt['APTLY_DEPLOY_NETMASK'] = context['deploy_network_netmask']
397 }
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000398 smc_apt['APTLY_MINION_ID'] = "${aptlyServerHostname}.${context['cluster_domain']}"
399
400 for (i in common.entries(smc_apt)) {
401 sh "sed -i \"s,export ${i[0]}=.*,export ${i[0]}=${i[1]},\" mirror_config"
402 }
403
404 // create apt config-drive
Denis Egorenkoaeaa0132019-02-25 16:55:08 +0400405 if (outdateGeneration) {
406 sh "./create-config-drive --user-data mirror_config --hostname ${aptlyServerHostname} ${aptlyServerHostname}.${context['cluster_domain']}-config.iso"
407 } else {
408 args = [
Denis Egorenko032a2b72019-04-03 14:56:52 +0400409 "--ip ${context['aptly_server_deploy_address']}", "--netmask ${deployNetworkSubnet}", "--gateway ${context['deploy_network_gateway']}",
Denis Egorenkoaaeda9b2019-02-28 20:55:59 +0400410 "--user-data mirror_config", "--hostname ${aptlyServerHostname}.${context['cluster_domain']}", "--name ${aptlyServerHostname}", "--clean-up",
411 "--dns-nameservers ${context['dns_server01']},${context['dns_server02']}"
Denis Egorenkoaeaa0132019-02-25 16:55:08 +0400412 ]
413 sh "python ./create-config-drive.py ${args.join(' ')}"
414 }
Aleksey Zvyagintsevaa235f22019-04-24 12:46:42 +0000415 sh("mv ${aptlyServerHostname}.${context['cluster_domain']}-config.iso output-${context['cluster_name']}/")
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000416
417 // save apt iso to artifacts
Aleksey Zvyagintsevaa235f22019-04-24 12:46:42 +0000418 archiveArtifacts artifacts: "output-${context['cluster_name']}/${aptlyServerHostname}.${context['cluster_domain']}-config.iso"
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000419 }
420 }
421
422 stage('Save changes reclass model') {
Denis Egorenkod9865252019-04-24 15:41:57 +0400423 sh(returnStatus: true, script: "tar -czf ${context['cluster_name']}.tar.gz --exclude='*@tmp' -C ${modelEnv} .")
424 archiveArtifacts artifacts: "${context['cluster_name']}.tar.gz"
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000425
azvyagintsev5400d1d2018-12-11 13:19:29 +0200426 if (RequesterEmail != '' && !RequesterEmail.contains('example')) {
Denis Egorenkof9729842019-07-08 15:53:01 +0400427 def mailSubject = "Your Salt model ${context['cluster_name']}"
428 if (context.get('send_method') == 'gcs') {
429 def gcs = new com.mirantis.mk.GoogleCloudStorage()
430 def uploadIsos = [ "output-${context['cluster_name']}/${context['salt_master_hostname']}.${context['cluster_domain']}-config.iso" ]
431 if (context['local_repositories'] == 'True') {
432 uploadIsos << "output-${context['cluster_name']}/${aptlyServerHostname}.${context['cluster_domain']}-config.iso"
433 }
434 // generate random hash to have uniq and unpredictable link to file
435 def randHash = common.generateRandomHashString(64)
436 def config = [
437 'creds': context['gcs_creds'],
438 'project': context['gcs_project'],
439 'dest': "gs://${context['gcs_bucket']}/${randHash}",
440 'sources': uploadIsos
441 ]
442 def fileURLs = gcs.uploadArtifactToGoogleStorageBucket(config).join(' ').replace('gs://', 'https://storage.googleapis.com/')
443 emailext(to: RequesterEmail,
444 body: "Mirantis Jenkins\n\nRequested reclass model ${context['cluster_name']} has been created and available to download via next URL: ${fileURLs} within 7 days.\nEnjoy!\n\nMirantis",
445 subject: mailSubject)
446 } else {
447 emailext(to: RequesterEmail,
448 attachmentsPattern: "output-${context['cluster_name']}/*",
449 body: "Mirantis Jenkins\n\nRequested reclass model ${context['cluster_name']} has been created and attached to this email.\nEnjoy!\n\nMirantis",
450 subject: mailSubject)
451 }
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000452 }
Aleksey Zvyagintsevaa235f22019-04-24 12:46:42 +0000453 dir("output-${context['cluster_name']}") {
454 deleteDir()
455 }
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000456 }
457
458 // Fail, but leave possibility to get failed artifacts
azvyagintsev6d678da2018-11-28 21:19:06 +0200459 if (!testResult && runTestModel) {
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000460 common.warningMsg('Test finished: FAILURE. Please check logs and\\or debug failed model manually!')
461 error('Test stage finished: FAILURE')
462 }
463
464 } catch (Throwable e) {
465 currentBuild.result = "FAILURE"
466 currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
467 throw e
468 } finally {
469 stage('Clean workspace directories') {
Aleksey Zvyagintsev7ca28d22019-02-25 11:05:49 +0000470 sh(script: 'find . -mindepth 1 -delete > /dev/null || true')
Aleksey Zvyagintsevb16902d2018-10-29 12:33:48 +0000471 }
472 // common.sendNotification(currentBuild.result,"",["slack"])
Denis Egorenko34c4a3b2019-03-12 12:48:15 +0400473 stage('Save artifacts to Artifactory') {
474 def artifactory = new com.mirantis.mcp.MCPArtifactory()
azvyagintsev06dfe402019-03-22 17:58:53 +0200475 def buildProps = ["context=${context['cluster_name']}"]
Denis Egorenko34c4a3b2019-03-12 12:48:15 +0400476 if (RequesterEmail != '' && !RequesterEmail.contains('example')) {
477 buildProps.add("emailTo=${RequesterEmail}")
478 }
479 def artifactoryLink = artifactory.uploadJobArtifactsToArtifactory([
azvyagintsev06dfe402019-03-22 17:58:53 +0200480 'artifactory' : 'mcp-ci',
Denis Egorenko34c4a3b2019-03-12 12:48:15 +0400481 'artifactoryRepo': "drivetrain-local/${JOB_NAME}/${context['cluster_name']}-${BUILD_NUMBER}",
azvyagintsev06dfe402019-03-22 17:58:53 +0200482 'buildProps' : buildProps,
Denis Egorenko34c4a3b2019-03-12 12:48:15 +0400483 ])
484 currentBuild.description += "<br/>${artifactoryLink}"
485 }
Ruslan Kamaldinov6feef402017-08-02 16:55:58 +0400486 }
Tomáš Kukrál7ded3642017-03-27 15:52:51 +0200487 }
Mikhail Ivanov9f812922017-11-07 18:52:02 +0400488}