1--- 2- name: set up aws connection info 3 set_fact: 4 aws_connection_info: &aws_connection_info 5 aws_access_key: "{{ aws_access_key | default(omit) }}" 6 aws_secret_key: "{{ aws_secret_key | default(omit) }}" 7 security_token: "{{ security_token | default(omit) }}" 8 region: "{{ aws_region | default(omit) }}" 9 no_log: yes 10 11- module_defaults: 12 cloudformation: 13 <<: *aws_connection_info 14 cloudformation_info: 15 <<: *aws_connection_info 16 17 block: 18 19 # ==== Env setup ========================================================== 20 - name: list available AZs 21 aws_az_info: 22 <<: *aws_connection_info 23 register: region_azs 24 25 - name: pick an AZ for testing 26 set_fact: 27 availability_zone: "{{ region_azs.availability_zones[0].zone_name }}" 28 29 - name: Create a test VPC 30 ec2_vpc_net: 31 name: "{{ vpc_name }}" 32 cidr_block: "{{ vpc_cidr }}" 33 tags: 34 Name: Cloudformation testing 35 <<: *aws_connection_info 36 register: testing_vpc 37 38 - name: Create a test subnet 39 ec2_vpc_subnet: 40 vpc_id: "{{ testing_vpc.vpc.id }}" 41 cidr: "{{ subnet_cidr }}" 42 az: "{{ availability_zone }}" 43 <<: *aws_connection_info 44 register: testing_subnet 45 46 - name: Find AMI to use 47 ec2_ami_info: 48 owners: 'amazon' 49 filters: 50 name: '{{ ec2_ami_name }}' 51 <<: *aws_connection_info 52 register: ec2_amis 53 54 - name: Set fact with latest AMI 55 vars: 56 latest_ami: '{{ ec2_amis.images | sort(attribute="creation_date") | last }}' 57 set_fact: 58 ec2_ami_image: '{{ latest_ami.image_id }}' 59 60 # ==== Cloudformation tests =============================================== 61 62 # 1. Basic stack creation (check mode, actual run and idempotency) 63 # 2. Tags 64 # 3. cloudformation_info tests (basic + all_facts) 65 # 4. termination_protection 66 # 5. create_changeset + changeset_name 67 68 # There is still scope to add tests for - 69 # 1. capabilities 70 # 2. stack_policy 71 # 3. on_create_failure (covered in unit tests) 72 # 4. Passing in a role 73 # 5. nested stacks? 74 75 76 - name: create a cloudformation stack (check mode) 77 cloudformation: 78 stack_name: "{{ stack_name }}" 79 template_body: "{{ lookup('file','cf_template.json') }}" 80 template_parameters: 81 InstanceType: "t3.nano" 82 ImageId: "{{ ec2_ami_image }}" 83 SubnetId: "{{ testing_subnet.subnet.id }}" 84 tags: 85 Stack: "{{ stack_name }}" 86 test: "{{ resource_prefix }}" 87 register: cf_stack 88 check_mode: yes 89 90 - name: check task return attributes 91 assert: 92 that: 93 - cf_stack.changed 94 - "'msg' in cf_stack and 'New stack would be created' in cf_stack.msg" 95 96 - name: create a cloudformation stack 97 cloudformation: 98 stack_name: "{{ stack_name }}" 99 template_body: "{{ lookup('file','cf_template.json') }}" 100 template_parameters: 101 InstanceType: "t3.nano" 102 ImageId: "{{ ec2_ami_image }}" 103 SubnetId: "{{ testing_subnet.subnet.id }}" 104 tags: 105 Stack: "{{ stack_name }}" 106 test: "{{ resource_prefix }}" 107 register: cf_stack 108 109 - name: check task return attributes 110 assert: 111 that: 112 - cf_stack.changed 113 - "'events' in cf_stack" 114 - "'output' in cf_stack and 'Stack CREATE complete' in cf_stack.output" 115 - "'stack_outputs' in cf_stack and 'InstanceId' in cf_stack.stack_outputs" 116 - "'stack_resources' in cf_stack" 117 118 - name: create a cloudformation stack (check mode) (idempotent) 119 cloudformation: 120 stack_name: "{{ stack_name }}" 121 template_body: "{{ lookup('file','cf_template.json') }}" 122 template_parameters: 123 InstanceType: "t3.nano" 124 ImageId: "{{ ec2_ami_image }}" 125 SubnetId: "{{ testing_subnet.subnet.id }}" 126 tags: 127 Stack: "{{ stack_name }}" 128 test: "{{ resource_prefix }}" 129 register: cf_stack 130 check_mode: yes 131 132 - name: check task return attributes 133 assert: 134 that: 135 - not cf_stack.changed 136 137 - name: create a cloudformation stack (idempotent) 138 cloudformation: 139 stack_name: "{{ stack_name }}" 140 template_body: "{{ lookup('file','cf_template.json') }}" 141 template_parameters: 142 InstanceType: "t3.nano" 143 ImageId: "{{ ec2_ami_image }}" 144 SubnetId: "{{ testing_subnet.subnet.id }}" 145 tags: 146 Stack: "{{ stack_name }}" 147 test: "{{ resource_prefix }}" 148 register: cf_stack 149 150 - name: check task return attributes 151 assert: 152 that: 153 - not cf_stack.changed 154 - "'output' in cf_stack and 'Stack is already up-to-date.' in cf_stack.output" 155 - "'stack_outputs' in cf_stack and 'InstanceId' in cf_stack.stack_outputs" 156 - "'stack_resources' in cf_stack" 157 158 - name: get stack details 159 cloudformation_info: 160 stack_name: "{{ stack_name }}" 161 register: stack_info 162 163 - name: assert stack info 164 assert: 165 that: 166 - "'cloudformation' in stack_info" 167 - "stack_info.cloudformation | length == 1" 168 - "stack_name in stack_info.cloudformation" 169 - "'stack_description' in stack_info.cloudformation[stack_name]" 170 - "'stack_outputs' in stack_info.cloudformation[stack_name]" 171 - "'stack_parameters' in stack_info.cloudformation[stack_name]" 172 - "'stack_tags' in stack_info.cloudformation[stack_name]" 173 - "stack_info.cloudformation[stack_name].stack_tags.Stack == stack_name" 174 175 - name: get stack details (checkmode) 176 cloudformation_info: 177 stack_name: "{{ stack_name }}" 178 register: stack_info 179 check_mode: yes 180 181 - name: assert stack info 182 assert: 183 that: 184 - "'cloudformation' in stack_info" 185 - "stack_info.cloudformation | length == 1" 186 - "stack_name in stack_info.cloudformation" 187 - "'stack_description' in stack_info.cloudformation[stack_name]" 188 - "'stack_outputs' in stack_info.cloudformation[stack_name]" 189 - "'stack_parameters' in stack_info.cloudformation[stack_name]" 190 - "'stack_tags' in stack_info.cloudformation[stack_name]" 191 - "stack_info.cloudformation[stack_name].stack_tags.Stack == stack_name" 192 193 - name: get stack details (all_facts) 194 cloudformation_info: 195 stack_name: "{{ stack_name }}" 196 all_facts: yes 197 register: stack_info 198 199 - name: assert stack info 200 assert: 201 that: 202 - "'stack_events' in stack_info.cloudformation[stack_name]" 203 - "'stack_policy' in stack_info.cloudformation[stack_name]" 204 - "'stack_resource_list' in stack_info.cloudformation[stack_name]" 205 - "'stack_resources' in stack_info.cloudformation[stack_name]" 206 - "'stack_template' in stack_info.cloudformation[stack_name]" 207 208 - name: get stack details (all_facts) (checkmode) 209 cloudformation_info: 210 stack_name: "{{ stack_name }}" 211 all_facts: yes 212 register: stack_info 213 check_mode: yes 214 215 - name: assert stack info 216 assert: 217 that: 218 - "'stack_events' in stack_info.cloudformation[stack_name]" 219 - "'stack_policy' in stack_info.cloudformation[stack_name]" 220 - "'stack_resource_list' in stack_info.cloudformation[stack_name]" 221 - "'stack_resources' in stack_info.cloudformation[stack_name]" 222 - "'stack_template' in stack_info.cloudformation[stack_name]" 223 224 # ==== Cloudformation tests (create changeset) ============================ 225 226 # try to create a changeset by changing instance type 227 - name: create a changeset 228 cloudformation: 229 stack_name: "{{ stack_name }}" 230 create_changeset: yes 231 changeset_name: "test-changeset" 232 template_body: "{{ lookup('file','cf_template.json') }}" 233 template_parameters: 234 InstanceType: "t3.micro" 235 ImageId: "{{ ec2_ami_image }}" 236 SubnetId: "{{ testing_subnet.subnet.id }}" 237 tags: 238 Stack: "{{ stack_name }}" 239 test: "{{ resource_prefix }}" 240 register: create_changeset_result 241 242 - name: assert changeset created 243 assert: 244 that: 245 - "create_changeset_result.changed" 246 - "'change_set_id' in create_changeset_result" 247 - "'Stack CREATE_CHANGESET complete' in create_changeset_result.output" 248 249 - name: get stack details with changesets 250 cloudformation_info: 251 stack_name: "{{ stack_name }}" 252 stack_change_sets: True 253 register: stack_info 254 255 - name: assert changesets in info 256 assert: 257 that: 258 - "'stack_change_sets' in stack_info.cloudformation[stack_name]" 259 260 - name: get stack details with changesets (checkmode) 261 cloudformation_info: 262 stack_name: "{{ stack_name }}" 263 stack_change_sets: True 264 register: stack_info 265 check_mode: yes 266 267 - name: assert changesets in info 268 assert: 269 that: 270 - "'stack_change_sets' in stack_info.cloudformation[stack_name]" 271 272 # try to create an empty changeset by passing in unchanged template 273 - name: create a changeset 274 cloudformation: 275 stack_name: "{{ stack_name }}" 276 create_changeset: yes 277 template_body: "{{ lookup('file','cf_template.json') }}" 278 template_parameters: 279 InstanceType: "t3.nano" 280 ImageId: "{{ ec2_ami_image }}" 281 SubnetId: "{{ testing_subnet.subnet.id }}" 282 tags: 283 Stack: "{{ stack_name }}" 284 test: "{{ resource_prefix }}" 285 register: create_changeset_result 286 287 - name: assert changeset created 288 assert: 289 that: 290 - "not create_changeset_result.changed" 291 - "'The created Change Set did not contain any changes to this stack and was deleted.' in create_changeset_result.output" 292 293 # ==== Cloudformation tests (termination_protection) ====================== 294 295 - name: set termination protection to true 296 cloudformation: 297 stack_name: "{{ stack_name }}" 298 termination_protection: yes 299 template_body: "{{ lookup('file','cf_template.json') }}" 300 template_parameters: 301 InstanceType: "t3.nano" 302 ImageId: "{{ ec2_ami_image }}" 303 SubnetId: "{{ testing_subnet.subnet.id }}" 304 tags: 305 Stack: "{{ stack_name }}" 306 test: "{{ resource_prefix }}" 307 register: cf_stack 308 309# This fails - #65592 310# - name: check task return attributes 311# assert: 312# that: 313# - cf_stack.changed 314 315 - name: get stack details 316 cloudformation_info: 317 stack_name: "{{ stack_name }}" 318 register: stack_info 319 320 - name: assert stack info 321 assert: 322 that: 323 - "stack_info.cloudformation[stack_name].stack_description.enable_termination_protection" 324 325 - name: get stack details (checkmode) 326 cloudformation_info: 327 stack_name: "{{ stack_name }}" 328 register: stack_info 329 check_mode: yes 330 331 - name: assert stack info 332 assert: 333 that: 334 - "stack_info.cloudformation[stack_name].stack_description.enable_termination_protection" 335 336 - name: set termination protection to false 337 cloudformation: 338 stack_name: "{{ stack_name }}" 339 termination_protection: no 340 template_body: "{{ lookup('file','cf_template.json') }}" 341 template_parameters: 342 InstanceType: "t3.nano" 343 ImageId: "{{ ec2_ami_image }}" 344 SubnetId: "{{ testing_subnet.subnet.id }}" 345 tags: 346 Stack: "{{ stack_name }}" 347 test: "{{ resource_prefix }}" 348 register: cf_stack 349 350# This fails - #65592 351# - name: check task return attributes 352# assert: 353# that: 354# - cf_stack.changed 355 356 - name: get stack details 357 cloudformation_info: 358 stack_name: "{{ stack_name }}" 359 register: stack_info 360 361 - name: assert stack info 362 assert: 363 that: 364 - "not stack_info.cloudformation[stack_name].stack_description.enable_termination_protection" 365 366 - name: get stack details (checkmode) 367 cloudformation_info: 368 stack_name: "{{ stack_name }}" 369 register: stack_info 370 check_mode: yes 371 372 - name: assert stack info 373 assert: 374 that: 375 - "not stack_info.cloudformation[stack_name].stack_description.enable_termination_protection" 376 377 # ==== Cloudformation tests (delete stack tests) ========================== 378 379 - name: delete cloudformation stack (check mode) 380 cloudformation: 381 stack_name: "{{ stack_name }}" 382 state: absent 383 check_mode: yes 384 register: cf_stack 385 386 - name: check task return attributes 387 assert: 388 that: 389 - cf_stack.changed 390 - "'msg' in cf_stack and 'Stack would be deleted' in cf_stack.msg" 391 392 - name: delete cloudformation stack 393 cloudformation: 394 stack_name: "{{ stack_name }}" 395 state: absent 396 register: cf_stack 397 398 - name: check task return attributes 399 assert: 400 that: 401 - cf_stack.changed 402 - "'output' in cf_stack and 'Stack Deleted' in cf_stack.output" 403 404 - name: delete cloudformation stack (check mode) (idempotent) 405 cloudformation: 406 stack_name: "{{ stack_name }}" 407 state: absent 408 check_mode: yes 409 register: cf_stack 410 411 - name: check task return attributes 412 assert: 413 that: 414 - not cf_stack.changed 415 - "'msg' in cf_stack" 416 - >- 417 "Stack doesn't exist" in cf_stack.msg 418 419 - name: delete cloudformation stack (idempotent) 420 cloudformation: 421 stack_name: "{{ stack_name }}" 422 state: absent 423 register: cf_stack 424 425 - name: check task return attributes 426 assert: 427 that: 428 - not cf_stack.changed 429 - "'output' in cf_stack and 'Stack not found.' in cf_stack.output" 430 431 - name: get stack details 432 cloudformation_info: 433 stack_name: "{{ stack_name }}" 434 register: stack_info 435 436 - name: assert stack info 437 assert: 438 that: 439 - "not stack_info.cloudformation" 440 441 - name: get stack details (checkmode) 442 cloudformation_info: 443 stack_name: "{{ stack_name }}" 444 register: stack_info 445 check_mode: yes 446 447 - name: assert stack info 448 assert: 449 that: 450 - "not stack_info.cloudformation" 451 452 # ==== Cleanup ============================================================ 453 454 always: 455 456 - name: delete stack 457 cloudformation: 458 stack_name: "{{ stack_name }}" 459 state: absent 460 ignore_errors: yes 461 462 - name: Delete test subnet 463 ec2_vpc_subnet: 464 vpc_id: "{{ testing_vpc.vpc.id }}" 465 cidr: "{{ subnet_cidr }}" 466 state: absent 467 <<: *aws_connection_info 468 ignore_errors: yes 469 470 - name: Delete test VPC 471 ec2_vpc_net: 472 name: "{{ vpc_name }}" 473 cidr_block: "{{ vpc_cidr }}" 474 state: absent 475 <<: *aws_connection_info 476 ignore_errors: yes 477