Skip to content

Commit 4c0344e

Browse files
committed
feat: add support for RHEL 8
1 parent 90d2fa2 commit 4c0344e

16 files changed

+593
-12
lines changed

.ansible-lint

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ exclude_paths:
55
- .github/
66
- .cache/
77
- molecule/default
8+
- molecule/aws-ec2
89
offline: false
910
use_default_rules: true
1011
parseable: true

.yamllint

-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
---
22
# Based on ansible-lint config
3-
43
extends: default
54

65
rules:
@@ -32,7 +31,3 @@ rules:
3231
type: unix
3332
trailing-spaces: disable
3433
truthy: disable
35-
36-
ignore: |
37-
.tox/
38-
venv

molecule/aws-ec2/INSTALL.rst

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
*********************************************
2+
Amazon Web Services driver installation guide
3+
*********************************************
4+
5+
Requirements
6+
============
7+
8+
* An AWS credentials rc file
9+
10+
Install
11+
=======
12+
13+
Please refer to the `Virtual environment`_ documentation for installation best
14+
practices. If not using a virtual environment, please consider passing the
15+
widely recommended `'--user' flag`_ when invoking ``pip``.
16+
17+
.. _Virtual environment: https://virtualenv.pypa.io/en/latest/
18+
.. _'--user' flag: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site
19+
20+
.. code-block:: bash
21+
22+
$ pip install 'molecule-ec2'

molecule/aws-ec2/converge.yml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
- name: Converge
3+
hosts: all
4+
become: true
5+
pre_tasks:
6+
- name: Include vars
7+
include_vars: "{{ playbook_dir }}/../../tests/vars/main.yml"
8+
tasks:
9+
- name: "Include ansible-python-install"
10+
include_role:
11+
name: "ansible-python-install"

molecule/aws-ec2/create.yml

+323
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
---
2+
- name: Create
3+
hosts: localhost
4+
connection: local
5+
gather_facts: false
6+
no_log: "{{ molecule_no_log }}"
7+
collections:
8+
- community.aws
9+
- community.crypto
10+
vars:
11+
# Run config handling
12+
default_run_id: "{{ lookup('password', '/dev/null chars=ascii_lowercase length=5') }}"
13+
default_run_config:
14+
run_id: "{{ default_run_id }}"
15+
16+
run_config_path: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/run-config.yml"
17+
run_config_from_file: "{{ (lookup('file', run_config_path, errors='ignore') or '{}') | from_yaml }}"
18+
run_config: '{{ default_run_config | combine(run_config_from_file) }}'
19+
20+
# Platform settings handling
21+
default_assign_public_ip: true
22+
default_aws_profile: "{{ lookup('env', 'AWS_PROFILE') }}"
23+
default_boot_wait_seconds: 120
24+
default_instance_type: t3a.medium
25+
default_key_inject_method: cloud-init # valid values: [cloud-init, ec2]
26+
default_key_name: "molecule-{{ run_config.run_id }}"
27+
default_private_key_path: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/id_rsa"
28+
default_public_key_path: "{{ default_private_key_path }}.pub"
29+
default_ssh_user: ec2-user
30+
default_ssh_port: 22
31+
default_user_data: ''
32+
33+
default_security_group_name: "molecule-{{ run_config.run_id }}"
34+
default_security_group_description: Ephemeral security group for Molecule instances
35+
default_security_group_rules:
36+
- proto: tcp
37+
from_port: "{{ default_ssh_port }}"
38+
to_port: "{{ default_ssh_port }}"
39+
cidr_ip: "0.0.0.0/0"
40+
- proto: icmp
41+
from_port: 8
42+
to_port: -1
43+
cidr_ip: "0.0.0.0/0"
44+
default_security_group_rules_egress:
45+
- proto: -1
46+
from_port: 0
47+
to_port: 0
48+
cidr_ip: "0.0.0.0/0"
49+
50+
platform_defaults:
51+
assign_public_ip: "{{ default_assign_public_ip }}"
52+
aws_profile: "{{ default_aws_profile }}"
53+
boot_wait_seconds: "{{ default_boot_wait_seconds }}"
54+
instance_type: "{{ default_instance_type }}"
55+
key_inject_method: "{{ default_key_inject_method }}"
56+
key_name: "{{ default_key_name }}"
57+
private_key_path: "{{ default_private_key_path }}"
58+
public_key_path: "{{ default_public_key_path }}"
59+
security_group_name: "{{ default_security_group_name }}"
60+
security_group_description: "{{ default_security_group_description }}"
61+
security_group_rules: "{{ default_security_group_rules }}"
62+
security_group_rules_egress: "{{ default_security_group_rules_egress }}"
63+
ssh_user: "{{ default_ssh_user }}"
64+
ssh_port: "{{ default_ssh_port }}"
65+
cloud_config: {}
66+
image: ""
67+
image_name: ""
68+
image_owner: [self]
69+
name: ""
70+
region: ""
71+
security_groups: []
72+
tags: {}
73+
volumes: []
74+
vpc_id: ""
75+
vpc_subnet_id: ""
76+
77+
# Merging defaults into a list of dicts is, it turns out, not straightforward
78+
platforms: >-
79+
{{ [platform_defaults | dict2items]
80+
| product(molecule_yml.platforms | map('dict2items') | list)
81+
| map('flatten', levels=1)
82+
| list
83+
| map('items2dict')
84+
| list }}
85+
pre_tasks:
86+
- name: Validate platform configurations
87+
assert:
88+
that:
89+
- platforms | length > 0
90+
- platform.name is string and platform.name | length > 0
91+
- platform.assign_public_ip is boolean
92+
- platform.aws_profile is string
93+
- platform.boot_wait_seconds is integer and platform.boot_wait_seconds >= 0
94+
- platform.cloud_config is mapping
95+
- platform.image is string
96+
- platform.image_name is string
97+
- platform.image_owner is sequence or (platform.image_owner is string and platform.image_owner | length > 0)
98+
- platform.instance_type is string and platform.instance_type | length > 0
99+
- platform.key_inject_method is in ["cloud-init", "ec2"]
100+
- platform.key_name is string and platform.key_name | length > 0
101+
- platform.private_key_path is string and platform.private_key_path | length > 0
102+
- platform.public_key_path is string and platform.public_key_path | length > 0
103+
- platform.region is string
104+
- platform.security_group_name is string and platform.security_group_name | length > 0
105+
- platform.security_group_description is string and platform.security_group_description | length > 0
106+
- platform.security_group_rules is sequence
107+
- platform.security_group_rules_egress is sequence
108+
- platform.security_groups is sequence
109+
- platform.ssh_user is string and platform.ssh_user | length > 0
110+
- platform.ssh_port is integer and platform.ssh_port in range(1, 65536)
111+
- platform.tags is mapping
112+
- platform.volumes is sequence
113+
- platform.vpc_id is string
114+
- platform.vpc_subnet_id is string and platform.vpc_subnet_id | length > 0
115+
quiet: true
116+
loop: '{{ platforms }}'
117+
loop_control:
118+
loop_var: platform
119+
label: "{{ platform.name }}"
120+
tasks:
121+
- name: Write run config to file
122+
copy:
123+
dest: "{{ run_config_path }}"
124+
content: "{{ run_config | to_yaml }}"
125+
126+
- name: Generate local key pairs
127+
openssh_keypair:
128+
path: "{{ item.private_key_path }}"
129+
type: rsa
130+
size: 2048
131+
regenerate: never
132+
loop: "{{ platforms }}"
133+
loop_control:
134+
label: "{{ item.name }}"
135+
register: local_keypairs
136+
137+
- name: Look up EC2 AMI(s) by owner and name (if image not set)
138+
ec2_ami_info:
139+
owners: "{{ item.image_owner }}"
140+
filters:
141+
name: "{{ item.image_name }}"
142+
loop: "{{ platforms }}"
143+
loop_control:
144+
label: "{{ item.name }}"
145+
when: not item.image
146+
register: ami_info
147+
148+
- name: Look up subnets to determine VPCs (if needed)
149+
ec2_vpc_subnet_info:
150+
subnet_ids: "{{ item.vpc_subnet_id }}"
151+
loop: "{{ platforms }}"
152+
loop_control:
153+
label: "{{ item.name }}"
154+
when: not item.vpc_id
155+
register: subnet_info
156+
157+
- name: Validate discovered information
158+
assert:
159+
that:
160+
- platform.image or (ami_info.results[index].images | length > 0)
161+
- platform.vpc_id or (subnet_info.results[index].subnets | length > 0)
162+
quiet: true
163+
loop: "{{ platforms }}"
164+
loop_control:
165+
loop_var: platform
166+
index_var: index
167+
label: "{{ platform.name }}"
168+
169+
- name: Create ephemeral EC2 keys (if needed)
170+
ec2_key:
171+
profile: "{{ item.aws_profile | default(omit) }}"
172+
region: "{{ item.region | default(omit) }}"
173+
name: "{{ item.key_name }}"
174+
key_material: "{{ local_keypair.public_key }}"
175+
vars:
176+
local_keypair: "{{ local_keypairs.results[index] }}"
177+
loop: "{{ platforms }}"
178+
loop_control:
179+
index_var: index
180+
label: "{{ item.name }}"
181+
when: item.key_inject_method == "ec2"
182+
register: ec2_keys
183+
184+
- name: Create ephemeral security groups (if needed)
185+
ec2_group:
186+
profile: "{{ item.aws_profile | default(omit) }}"
187+
region: "{{ item.region | default(omit) }}"
188+
vpc_id: "{{ item.vpc_id or vpc_subnet.vpc_id }}"
189+
name: "{{ item.security_group_name }}"
190+
description: "{{ item.security_group_description }}"
191+
rules: "{{ item.security_group_rules }}"
192+
rules_egress: "{{ item.security_group_rules_egress }}"
193+
vars:
194+
vpc_subnet: "{{ subnet_info.results[index].subnets[0] }}"
195+
loop: "{{ platforms }}"
196+
loop_control:
197+
index_var: index
198+
label: "{{ item.name }}"
199+
when: item.security_groups | length == 0
200+
201+
- name: Create ephemeral EC2 instance(s)
202+
ec2_instance:
203+
profile: "{{ item.aws_profile | default(omit) }}"
204+
region: "{{ item.region | default(omit) }}"
205+
filters: "{{ platform_filters }}"
206+
instance_type: "{{ item.instance_type }}"
207+
image_id: "{{ platform_image_id }}"
208+
vpc_subnet_id: "{{ item.vpc_subnet_id }}"
209+
security_groups: "{{ platform_security_groups }}"
210+
network:
211+
assign_public_ip: "{{ item.assign_public_ip }}"
212+
volumes: "{{ item.volumes }}"
213+
key_name: "{{ (item.key_inject_method == 'ec2') | ternary(item.key_name, omit) }}"
214+
tags: "{{ platform_tags }}"
215+
user_data: "{{ platform_user_data }}"
216+
wait: true
217+
vars:
218+
platform_security_groups: "{{ item.security_groups or [item.security_group_name] }}"
219+
platform_generated_image_id: "{{ (ami_info.results[index].images | sort(attribute='creation_date', reverse=True))[0].image_id }}"
220+
platform_image_id: "{{ item.image or platform_generated_image_id }}"
221+
222+
platform_generated_cloud_config:
223+
users:
224+
- name: "{{ item.ssh_user }}"
225+
ssh_authorized_keys:
226+
- "{{ local_keypairs.results[index].public_key }}"
227+
sudo: "ALL=(ALL) NOPASSWD:ALL"
228+
platform_cloud_config: >-
229+
{{ (item.key_inject_method == 'cloud-init')
230+
| ternary((item.cloud_config | combine(platform_generated_cloud_config)), item.cloud_config) }}
231+
platform_user_data: |-
232+
#cloud-config
233+
{{ platform_cloud_config | to_yaml }}
234+
235+
platform_generated_tags:
236+
instance: "{{ item.name }}"
237+
"molecule-run-id": "{{ run_config.run_id }}"
238+
platform_tags: "{{ (item.tags or {}) | combine(platform_generated_tags) }}"
239+
platform_filter_keys: "{{ platform_generated_tags.keys() | map('regex_replace', '^(.+)$', 'tag:\\1') }}"
240+
platform_filters: "{{ dict(platform_filter_keys | zip(platform_generated_tags.values())) }}"
241+
loop: "{{ platforms }}"
242+
loop_control:
243+
index_var: index
244+
label: "{{ item.name }}"
245+
register: ec2_instances_async
246+
async: 7200
247+
poll: 0
248+
249+
- block:
250+
- name: Wait for instance creation to complete
251+
async_status:
252+
jid: "{{ item.ansible_job_id }}"
253+
loop: "{{ ec2_instances_async.results }}"
254+
loop_control:
255+
index_var: index
256+
label: "{{ platforms[index].name }}"
257+
register: ec2_instances
258+
until: ec2_instances is finished
259+
retries: 300
260+
261+
- name: Wait for public IP
262+
ansible.builtin.pause:
263+
seconds: 120
264+
265+
- name: Collect instance configs
266+
set_fact:
267+
instance_config:
268+
instance: "{{ item.name }}"
269+
address: "{{ item.assign_public_ip | ternary(instance.public_ip_address, instance.private_ip_address) }}"
270+
user: "{{ item.ssh_user }}"
271+
port: "{{ item.ssh_port }}"
272+
identity_file: "{{ item.private_key_path }}"
273+
instance_ids:
274+
- "{{ instance.instance_id }}"
275+
vars:
276+
instance: "{{ ec2_instances.results[index].instances[0] }}"
277+
loop: "{{ platforms }}"
278+
loop_control:
279+
index_var: index
280+
label: "{{ item.name }}"
281+
register: instance_configs
282+
283+
- name: Write Molecule instance configs
284+
copy:
285+
dest: "{{ molecule_instance_config }}"
286+
content: >-
287+
{{ instance_configs.results
288+
| map(attribute='ansible_facts.instance_config')
289+
| list
290+
| to_json
291+
| from_json
292+
| to_yaml }}
293+
294+
- name: Start SSH pollers
295+
wait_for:
296+
host: "{{ item.address }}"
297+
port: "{{ item.port }}"
298+
search_regex: SSH
299+
delay: 10
300+
timeout: 320
301+
loop: "{{ instance_configs.results | map(attribute='ansible_facts.instance_config') | list }}"
302+
loop_control:
303+
label: "{{ item.instance }}"
304+
register: ssh_wait_async
305+
async: 300
306+
poll: 0
307+
308+
- name: Wait for SSH
309+
async_status:
310+
jid: "{{ item.ansible_job_id }}"
311+
loop: "{{ ssh_wait_async.results }}"
312+
loop_control:
313+
index_var: index
314+
label: "{{ platforms[index].name }}"
315+
register: ssh_wait
316+
until: ssh_wait_async is finished
317+
retries: 300
318+
delay: 1
319+
320+
- name: Wait for boot process to finish
321+
pause:
322+
seconds: "{{ platforms | map(attribute='boot_wait_seconds') | max }}"
323+
when: ec2_instances_async is changed

0 commit comments

Comments
 (0)