blob: 37643cf90541ce5ce19ac7c9feb5b2737b23028a [file] [log] [blame]
Filip Pytloun0a07f702017-02-24 18:26:18 +01001/**
2 *
3 * Launch heat stack with CI/CD lab infrastructure
4 *
5 * Expected parameters:
6 * HEAT_TEMPLATE_URL URL to git repo with Heat templates
7 * HEAT_TEMPLATE_CREDENTIALS Credentials to the Heat templates repo
8 * HEAT_TEMPLATE_BRANCH Heat templates repo branch
9 * HEAT_STACK_NAME Heat stack name
10 * HEAT_STACK_TEMPLATE Heat stack HOT template
11 * HEAT_STACK_ENVIRONMENT Heat stack environmental parameters
12 * HEAT_STACK_ZONE Heat stack availability zone
13 * HEAT_STACK_PUBLIC_NET Heat stack floating IP pool
14 * HEAT_STACK_DELETE Delete Heat stack when finished (bool)
15 * HEAT_STACK_CLEANUP_JOB Name of job for deleting Heat stack
16 * HEAT_STACK_REUSE Reuse Heat stack (don't create one)
17 *
18 * SALT_MASTER_CREDENTIALS Credentials to the Salt API
Filip Pytloune32fda82017-02-24 18:26:18 +010019 * SALT_MASTER_PORT Port of salt-api, defaults to 8000
Filip Pytloun0a07f702017-02-24 18:26:18 +010020 *
21 * OPENSTACK_API_URL OpenStack API address
22 * OPENSTACK_API_CREDENTIALS Credentials to the OpenStack API
23 * OPENSTACK_API_PROJECT OpenStack project to connect to
24 * OPENSTACK_API_CLIENT Versions of OpenStack python clients
25 * OPENSTACK_API_VERSION Version of the OpenStack API (2/3)
26 *
27 */
28
Filip Pytlounad2b36b2017-03-04 20:33:41 +010029common = new com.mirantis.mk.Common()
Filip Pytloun0a07f702017-02-24 18:26:18 +010030git = new com.mirantis.mk.Git()
31openstack = new com.mirantis.mk.Openstack()
32salt = new com.mirantis.mk.Salt()
33orchestrate = new com.mirantis.mk.Orchestrate()
Jakub Josef458913d2017-05-10 15:37:56 +020034_MAX_PERMITTED_STACKS = 2
Filip Pytlounbfce09d2017-03-01 19:00:43 +010035timestamps {
36 node {
37 try {
38 // connection objects
39 def openstackCloud
40 def saltMaster
Filip Pytloun0a07f702017-02-24 18:26:18 +010041
Filip Pytlounbfce09d2017-03-01 19:00:43 +010042 // value defaults
43 def openstackVersion = OPENSTACK_API_CLIENT ? OPENSTACK_API_CLIENT : 'liberty'
44 def openstackEnv = "${env.WORKSPACE}/venv"
Filip Pytloun0a07f702017-02-24 18:26:18 +010045
Filip Pytloun3eefd3d2017-03-03 14:13:41 +010046 try {
47 sshPubKey = SSH_PUBLIC_KEY
48 } catch (MissingPropertyException e) {
49 sshPubKey = false
50 }
51
Filip Pytloun794ad952017-03-03 10:39:26 +010052 if (HEAT_STACK_REUSE.toBoolean() == true && HEAT_STACK_NAME == '') {
53 error("If you want to reuse existing stack you need to provide it's name")
54 }
55
56 if (HEAT_STACK_REUSE.toBoolean() == false) {
57 // Don't allow to set custom heat stack name
58 wrap([$class: 'BuildUser']) {
Tomáš Kukrál24d7fe62017-03-03 10:57:11 +010059 if (env.BUILD_USER_ID) {
60 HEAT_STACK_NAME = "${env.BUILD_USER_ID}-${JOB_NAME}-${BUILD_NUMBER}"
61 } else {
62 HEAT_STACK_NAME = "jenkins-${JOB_NAME}-${BUILD_NUMBER}"
63 }
Filip Pytloun794ad952017-03-03 10:39:26 +010064 currentBuild.description = HEAT_STACK_NAME
65 }
Filip Pytlounfd6726a2017-02-28 19:31:16 +010066 }
Filip Pytloun5b0954b2017-03-01 10:10:18 +010067
Filip Pytloun3d045f82017-03-01 09:44:52 +010068 //
Filip Pytlounbfce09d2017-03-01 19:00:43 +010069 // Bootstrap
Filip Pytloun3d045f82017-03-01 09:44:52 +010070 //
Filip Pytlounbfce09d2017-03-01 19:00:43 +010071
72 stage ('Download Heat templates') {
73 git.checkoutGitRepository('template', HEAT_TEMPLATE_URL, HEAT_TEMPLATE_BRANCH, HEAT_TEMPLATE_CREDENTIALS)
Filip Pytloun3d045f82017-03-01 09:44:52 +010074 }
Filip Pytloun3d045f82017-03-01 09:44:52 +010075
Filip Pytlounbfce09d2017-03-01 19:00:43 +010076 stage('Install OpenStack CLI') {
77 openstack.setupOpenstackVirtualenv(openstackEnv, openstackVersion)
78 }
Filip Pytloun64123cd2017-03-01 11:26:17 +010079
Filip Pytlounbfce09d2017-03-01 19:00:43 +010080 stage('Connect to OpenStack cloud') {
Vladislav Naumov822b7392017-07-25 17:57:21 +030081 openstackCloud = openstack.createOpenstackEnv(
82 OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS,
83 OPENSTACK_API_PROJECT, OPENSTACK_API_PROJECT_DOMAIN,
84 OPENSTACK_API_PROJECT_ID, OPENSTACK_API_USER_DOMAIN,
85 OPENSTACK_API_VERSION)
Filip Pytlounbfce09d2017-03-01 19:00:43 +010086 openstack.getKeystoneToken(openstackCloud, openstackEnv)
Jakub Josef458913d2017-05-10 15:37:56 +020087 wrap([$class: 'BuildUser']) {
Tomáš Kukrálab2f3702017-05-11 09:17:43 +020088 if (env.BUILD_USER_ID && !env.BUILD_USER_ID.equals("jenkins") && !HEAT_STACK_REUSE.toBoolean()) {
Jakub Josef78c3f8b2017-05-10 15:45:29 +020089 def existingStacks = openstack.getStacksForNameContains(openstackCloud, "${env.BUILD_USER_ID}-${JOB_NAME}", openstackEnv)
90 if(existingStacks.size() >= _MAX_PERMITTED_STACKS){
Jakub Josef124403a2017-05-10 15:58:06 +020091 HEAT_STACK_DELETE = "false"
Jakub Josef78c3f8b2017-05-10 15:45:29 +020092 throw new Exception("You cannot create new stack, you already have ${_MAX_PERMITTED_STACKS} stacks of this type (${JOB_NAME}). \nStack names: ${existingStacks}")
93 }
Jakub Josef458913d2017-05-10 15:37:56 +020094 }
95 }
Filip Pytlounbfce09d2017-03-01 19:00:43 +010096 }
97
Filip Pytloun794ad952017-03-03 10:39:26 +010098 if (HEAT_STACK_REUSE.toBoolean() == false) {
Filip Pytlounbfce09d2017-03-01 19:00:43 +010099 stage('Launch new Heat stack') {
100 envParams = [
101 'instance_zone': HEAT_STACK_ZONE,
102 'public_net': HEAT_STACK_PUBLIC_NET
103 ]
104 openstack.createHeatStack(openstackCloud, HEAT_STACK_NAME, HEAT_STACK_TEMPLATE, envParams, HEAT_STACK_ENVIRONMENT, openstackEnv)
105 }
106 }
107
108 stage('Connect to Salt master') {
109 def saltMasterPort
110 try {
111 saltMasterPort = SALT_MASTER_PORT
112 } catch (MissingPropertyException e) {
Filip Pytloun2ef26132017-03-10 09:44:37 +0100113 saltMasterPort = 6969
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100114 }
115 saltMasterHost = openstack.getHeatStackOutputParam(openstackCloud, HEAT_STACK_NAME, 'salt_master_ip', openstackEnv)
Jakub Josefbde0d442017-04-07 16:32:58 +0200116 currentBuild.description = "${HEAT_STACK_NAME}: ${saltMasterHost}"
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100117 saltMasterUrl = "http://${saltMasterHost}:${saltMasterPort}"
118 saltMaster = salt.connection(saltMasterUrl, SALT_MASTER_CREDENTIALS)
119 }
120
121 //
122 // Install
123 //
124
125 stage('Install core infra') {
126 // salt.master, reclass
127 // refresh_pillar
128 // sync_all
129 // linux,openssh,salt.minion.ntp
130
131 orchestrate.installFoundationInfra(saltMaster)
132 orchestrate.validateFoundationInfra(saltMaster)
133 }
134
135 stage("Deploy GlusterFS") {
136 salt.enforceState(saltMaster, 'I@glusterfs:server', 'glusterfs.server.service', true)
Filip Pytloun97e6fff2017-03-30 16:56:11 +0200137 retry(2) {
138 salt.enforceState(saltMaster, 'ci01*', 'glusterfs.server.setup', true)
139 }
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100140 sleep(5)
141 salt.enforceState(saltMaster, 'I@glusterfs:client', 'glusterfs.client', true)
Filip Pytloun5555b452017-04-19 12:41:44 +0200142
143 timeout(5) {
144 println "Waiting for GlusterFS volumes to get mounted.."
145 salt.cmdRun(saltMaster, 'I@glusterfs:client', 'while true; do systemctl -a|grep "GlusterFS File System"|grep -v mounted >/dev/null || break; done')
146 }
Jakub Joseffafd6592017-03-27 18:53:17 +0200147 print common.prettyPrint(salt.cmdRun(saltMaster, 'I@glusterfs:client', 'mount|grep fuse.glusterfs || echo "Command failed"'))
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100148 }
149
150 stage("Deploy GlusterFS") {
151 salt.enforceState(saltMaster, 'I@haproxy:proxy', 'haproxy,keepalived')
152 }
153
154 stage("Setup Docker Swarm") {
155 salt.enforceState(saltMaster, 'I@docker:host', 'docker.host', true)
156 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'docker.swarm', true)
157 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'salt', true)
158 salt.runSaltProcessStep(saltMaster, 'I@docker:swarm:role:master', 'mine.flush')
159 salt.runSaltProcessStep(saltMaster, 'I@docker:swarm:role:master', 'mine.update')
160 salt.enforceState(saltMaster, 'I@docker:swarm', 'docker.swarm', true)
Jakub Joseffafd6592017-03-27 18:53:17 +0200161 print common.prettyPrint(salt.cmdRun(saltMaster, 'I@docker:swarm:role:master', 'docker node ls'))
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100162 }
163
Ilya Kharin04c09982017-03-30 14:46:20 +0400164 stage("Configure OSS services") {
165 salt.enforceState(saltMaster, 'I@devops_portal:config', 'devops_portal.config')
Ilya Kharin7a18c322017-04-24 18:49:34 +0400166 salt.enforceState(saltMaster, 'I@rundeck:server', 'rundeck.server')
Ilya Kharin04c09982017-03-30 14:46:20 +0400167 }
168
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100169 stage("Deploy Docker services") {
Filip Pytloun82d628a2017-06-05 13:14:32 +0200170 // We need /etc/aptly-publisher.yaml to be present before
171 // services are deployed
Filip Pytloundf7823c2017-06-14 15:22:40 +0200172 // XXX: for some weird unknown reason, refresh_pillar is
173 // required to execute here
Filip Pytlounaebfa9c2017-06-14 16:12:57 +0200174 salt.runSaltProcessStep(saltMaster, 'I@aptly:publisher', 'saltutil.refresh_pillar', [], null, true)
Filip Pytloun82d628a2017-06-05 13:14:32 +0200175 salt.enforceState(saltMaster, 'I@aptly:publisher', 'aptly.publisher', true)
Filip Pytloun65b928d2017-04-18 17:19:30 +0200176 retry(3) {
Filip Pytlound6d18502017-04-13 15:35:07 +0200177 sleep(5)
178 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'docker.client')
179 }
Ilya Kharin10e0ae32017-07-07 01:27:59 +0400180 // XXX: Workaround to have `/var/lib/jenkins` on all
181 // nodes where are jenkins_slave services are created.
182 salt.runSaltProcessStep(saltMaster, 'I@docker:swarm', 'cmd.run', ['mkdir -p /var/lib/jenkins'])
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100183 }
184
185 stage("Configure CI/CD services") {
Filip Pytloun29d0bc12017-03-10 14:39:26 +0100186 salt.syncAll(saltMaster, '*')
187
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100188 // Aptly
Filip Pytloun6cde7882017-03-28 17:22:18 +0200189 timeout(10) {
190 println "Waiting for Aptly to come up.."
Filip Pytlounb1ddf322017-05-12 16:18:31 +0200191 retry(2) {
192 // XXX: retry to workaround magical VALUE_TRIMMED
193 // response from salt master + to give slow cloud some
194 // more time to settle down
195 salt.cmdRun(saltMaster, 'I@aptly:server', 'while true; do curl -sf http://172.16.10.254:8084/api/version >/dev/null && break; done')
196 }
Filip Pytloun6cde7882017-03-28 17:22:18 +0200197 }
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100198 salt.enforceState(saltMaster, 'I@aptly:server', 'aptly', true)
199
Filip Pytloun03983812017-03-28 13:07:34 +0200200 // OpenLDAP
201 timeout(10) {
202 println "Waiting for OpenLDAP to come up.."
Filip Pytlounb1ddf322017-05-12 16:18:31 +0200203 salt.cmdRun(saltMaster, 'I@openldap:client', 'while true; do curl -sf ldap://172.16.10.254 >/dev/null && break; done')
Filip Pytloun03983812017-03-28 13:07:34 +0200204 }
205 salt.enforceState(saltMaster, 'I@openldap:client', 'openldap', true)
206
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100207 // Gerrit
208 timeout(10) {
209 println "Waiting for Gerrit to come up.."
Filip Pytlounb1ddf322017-05-12 16:18:31 +0200210 salt.cmdRun(saltMaster, 'I@gerrit:client', 'while true; do curl -sf 172.16.10.254:8080 >/dev/null && break; done')
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100211 }
Filip Pytlounb1ddf322017-05-12 16:18:31 +0200212 salt.enforceState(saltMaster, 'I@gerrit:client', 'gerrit', true)
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100213
214 // Jenkins
215 timeout(10) {
216 println "Waiting for Jenkins to come up.."
Filip Pytlounb1ddf322017-05-12 16:18:31 +0200217 salt.cmdRun(saltMaster, 'I@jenkins:client', 'while true; do curl -sf 172.16.10.254:8081 >/dev/null && break; done')
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100218 }
Filip Pytloun45d40742017-05-12 18:19:44 +0200219 retry(2) {
220 // XXX: needs retry as first run installs python-jenkins
221 // thus make jenkins modules available for second run
222 salt.enforceState(saltMaster, 'I@jenkins:client', 'jenkins', true)
223 }
Ilya Kharin7a18c322017-04-24 18:49:34 +0400224
Volodymyr Stoiko75e341e2017-05-30 01:45:21 +0300225 // Postgres client - initialize OSS services databases
226 timeout(300){
227 println "Waiting for postgresql database to come up.."
Ilya Kharin51a5d972017-06-28 13:36:30 +0400228 salt.cmdRun(saltMaster, 'I@postgresql:client', 'while true; do if docker service logs postgresql_db | grep "ready to accept"; then break; else sleep 5; fi; done')
Volodymyr Stoiko75e341e2017-05-30 01:45:21 +0300229 }
Ilya Kharin51a5d972017-06-28 13:36:30 +0400230 salt.enforceState(saltMaster, 'I@postgresql:client', 'postgresql.client', true, false)
Volodymyr Stoiko75e341e2017-05-30 01:45:21 +0300231
232 // Setup postgres database with integration between
233 // Pushkin notification service and Security Monkey security audit service
234 timeout(10) {
235 println "Waiting for Pushkin to come up.."
Ilya Kharin51a5d972017-06-28 13:36:30 +0400236 salt.cmdRun(saltMaster, 'I@postgresql:client', 'while true; do curl -sf 172.16.10.254:8887/apps >/dev/null && break; done')
Volodymyr Stoiko75e341e2017-05-30 01:45:21 +0300237 }
Ilya Kharin51a5d972017-06-28 13:36:30 +0400238 salt.enforceState(saltMaster, 'I@postgresql:client', 'postgresql.client', true)
Volodymyr Stoiko75e341e2017-05-30 01:45:21 +0300239
Ilya Kharin7a18c322017-04-24 18:49:34 +0400240 // Rundeck
241 timeout(10) {
242 println "Waiting for Rundeck to come up.."
Filip Pytlounb1ddf322017-05-12 16:18:31 +0200243 salt.cmdRun(saltMaster, 'I@rundeck:client', 'while true; do curl -sf 172.16.10.254:4440 >/dev/null && break; done')
Ilya Kharin7a18c322017-04-24 18:49:34 +0400244 }
Filip Pytlounb1ddf322017-05-12 16:18:31 +0200245 salt.enforceState(saltMaster, 'I@rundeck:client', 'rundeck.client', true)
Volodymyr Stoikod73a8d42017-07-12 15:49:34 +0300246
247 // Elasticsearch
248 timeout(10) {
249 println 'Waiting for Elasticsearch to come up..'
250 salt.cmdRun(saltMaster, 'I@elasticsearch:client', 'while true; do curl -sf 172.16.10.254:9200 >/dev/null && break; done')
251 }
252 salt.enforceState(saltMaster, 'I@elasticsearch:client', 'elasticsearch.client', true)
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100253 }
254
255 stage("Finalize") {
256 //
Filip Pytloun3eefd3d2017-03-03 14:13:41 +0100257 // Deploy user's ssh key
258 //
Filip Pytloun0da421f2017-03-03 18:50:45 +0100259 def adminUser
260 def authorizedKeysFile
Filip Pytloun9935af02017-05-15 18:09:17 +0200261 def adminUserCmdOut = salt.cmdRun(saltMaster, 'I@salt:master', "[ ! -d /home/ubuntu ] || echo 'ubuntu user exists'")
Filip Pytlounbfa918a2017-03-04 10:01:30 +0100262 if (adminUserCmdOut =~ /ubuntu user exists/) {
Filip Pytloun0da421f2017-03-03 18:50:45 +0100263 adminUser = "ubuntu"
264 authorizedKeysFile = "/home/ubuntu/.ssh/authorized_keys"
265 } else {
266 adminUser = "root"
267 authorizedKeysFile = "/root/.ssh/authorized_keys"
268 }
Filip Pytloun3eefd3d2017-03-03 14:13:41 +0100269
Filip Pytloun0da421f2017-03-03 18:50:45 +0100270 if (sshPubKey) {
Filip Pytloun3eefd3d2017-03-03 14:13:41 +0100271 println "Deploying provided ssh key at ${authorizedKeysFile}"
Filip Pytloun4a847d62017-03-03 15:54:56 +0100272 salt.cmdRun(saltMaster, '*', "echo '${sshPubKey}' | tee -a ${authorizedKeysFile}")
Filip Pytloun3eefd3d2017-03-03 14:13:41 +0100273 }
274
275 //
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100276 // Generate docs
277 //
278 try {
Filip Pytloun64ff0792017-03-07 16:47:46 +0100279 try {
280 // Run sphinx state to install sphinx-build needed in
281 // upcomming orchestrate
282 salt.enforceState(saltMaster, 'I@sphinx:server', 'sphinx')
283 } catch (Throwable e) {
284 true
285 }
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100286 retry(3) {
Filip Pytloun27e8fa02017-03-01 20:02:46 +0100287 // TODO: fix salt.orchestrateSystem
288 // print salt.orchestrateSystem(saltMaster, ['expression': '*', 'type': 'compound'], 'sphinx.orch.generate_doc')
Filip Pytlounc17161d2017-03-03 09:50:54 +0100289 def out = salt.cmdRun(saltMaster, 'I@salt:master', 'salt-run state.orchestrate sphinx.orch.generate_doc || echo "Command execution failed"')
Jakub Joseffafd6592017-03-27 18:53:17 +0200290 print common.prettyPrint(out)
Filip Pytlounc17161d2017-03-03 09:50:54 +0100291 if (out =~ /Command execution failed/) {
292 throw new Exception("Command execution failed")
293 }
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100294 }
295 } catch (Throwable e) {
296 // We don't want sphinx docs to ruin whole build, so possible
297 // errors are just ignored here
298 true
299 }
300 salt.enforceState(saltMaster, 'I@nginx:server', 'nginx')
301
Filip Pytlouna4000402017-05-16 10:12:02 +0200302 def failedSvc = salt.cmdRun(saltMaster, '*', """systemctl --failed | grep -E 'loaded[ \t]+failed' && echo 'Command execution failed' || true""")
Jakub Joseffafd6592017-03-27 18:53:17 +0200303 print common.prettyPrint(failedSvc)
Filip Pytlounbd619272017-03-22 12:21:01 +0100304 if (failedSvc =~ /Command execution failed/) {
305 common.errorMsg("Some services are not running. Environment may not be fully functional!")
306 }
307
Filip Pytlound9427392017-03-04 13:58:08 +0100308 common.successMsg("""
Filip Pytloun794ad952017-03-03 10:39:26 +0100309 ============================================================
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100310 Your CI/CD lab has been deployed and you can enjoy it:
Filip Pytloun3eefd3d2017-03-03 14:13:41 +0100311 Use sshuttle to connect to your private subnet:
312
Filip Pytloun85464052017-03-03 16:31:43 +0100313 sshuttle -r ${adminUser}@${saltMasterHost} 172.16.10.0/24
Filip Pytloun3eefd3d2017-03-03 14:13:41 +0100314
315 And visit services running at 172.16.10.254 (vip address):
316
Ilya Kharin04c09982017-03-30 14:46:20 +0400317 9600 HAProxy statistics
318 8080 Gerrit
319 8081 Jenkins
Filip Pytlound0d700d2017-03-29 11:15:42 +0200320 8089 LDAP administration
Ilya Kharin04c09982017-03-30 14:46:20 +0400321 4440 Rundeck
322 8084 DevOps Portal
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100323 8091 Docker swarm visualizer
324 8090 Reclass-generated documentation
325
Filip Pytloun3eefd3d2017-03-03 14:13:41 +0100326 If you provided SSH_PUBLIC_KEY, you can use it to login,
327 otherwise you need to get private key connected to this
328 heat template.
329
330 DON'T FORGET TO TERMINATE YOUR STACK WHEN YOU DON'T NEED IT!
Filip Pytloun85464052017-03-03 16:31:43 +0100331 ============================================================""")
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100332 }
333 } catch (Throwable e) {
334 // If there was an error or exception thrown, the build failed
335 currentBuild.result = "FAILURE"
336 throw e
337 } finally {
338 // Cleanup
Filip Pytloun794ad952017-03-03 10:39:26 +0100339 if (HEAT_STACK_DELETE.toBoolean() == true) {
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100340 stage('Trigger cleanup job') {
Ilya Kharin38b261d2017-06-29 01:42:59 +0400341 build(job: 'deploy-stack-cleanup', parameters: [
342 [$class: 'StringParameterValue', name: 'STACK_NAME', value: HEAT_STACK_NAME],
343 [$class: 'StringParameterValue', name: 'OPENSTACK_API_PROJECT', value: OPENSTACK_API_PROJECT],
344 ])
Filip Pytlounbfce09d2017-03-01 19:00:43 +0100345 }
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100346 }
Filip Pytloun23741982017-02-27 17:43:00 +0100347 }
Filip Pytlounf6e877f2017-02-28 19:38:16 +0100348 }
Filip Pytloun0a07f702017-02-24 18:26:18 +0100349}