Skip to content

Commit

Permalink
Adding AWS EC2 to CI pipeline (#116)
Browse files Browse the repository at this point in the history
Co-authored-by: tommyd450 <[email protected]>
  • Loading branch information
SequeI and tommyd450 authored Dec 13, 2024
1 parent 4787905 commit 9d406c2
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 111 deletions.
22 changes: 15 additions & 7 deletions .github/workflows/molecule.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ on:

jobs:
molecule:
runs-on: testing-farm
runs-on: ubuntu-latest
strategy:
matrix:
scenario:
- default
- user_provided
max-parallel: 1
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -24,22 +25,29 @@ jobs:
pip install -r testing-requirements.txt
ansible-galaxy install -r requirements.yml
ansible-galaxy install -r molecule/requirements.yml
- name: Set up SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.AWS_SSH_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
- name: Run molecule
env:
TAS_SINGLE_NODE_REGISTRY_USERNAME: ${{ secrets.TAS_SINGLE_NODE_REGISTRY_USERNAME }}
TAS_SINGLE_NODE_REGISTRY_PASSWORD: ${{ secrets.TAS_SINGLE_NODE_REGISTRY_PASSWORD }}
TESTING_FARM_API_TOKEN: ${{ secrets.TESTING_FARM_API_TOKEN }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
GITHUB_RUN_ID: ${{ github.run_id }}
run: |
eval $(ssh-agent -s) # testing-farm CLI right now mandates that ssh-agent is running
source venv/bin/activate
# ensure we wipe out any data potentially still on the runner from previous run
molecule reset
# NOTE: for now we don't run "molecule test", because we don't have proper idempotence yet
molecule -v test --scenario-name ${{ matrix.scenario }}
- name: Destroy molecule infrastructure
env:
TESTING_FARM_API_TOKEN: ${{ secrets.TESTING_FARM_API_TOKEN }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
if: always()
run:
run: |
source venv/bin/activate
molecule destroy
266 changes: 195 additions & 71 deletions molecule/default/create.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
# TODO: improve idempotency and error handling throughout this file,
# specifically for the testing-farm call
- name: Create
hosts: localhost
connection: local
gather_facts: false
vars_files:
- vars/vars.yml
vars:
debug_outputs: true
molecule_inventory:
Expand All @@ -15,86 +15,203 @@
tasks:
- name: Inform user we're about to create the VM
ansible.builtin.debug:
msg: Creating Testing Farm VM instance
msg: Creating AWS EC2 VM instance

- name: Define private key path
- name: Gather VPC facts
amazon.aws.ec2_vpc_net_info:
region: "{{ lookup('env', 'AWS_DEFAULT_REGION') }}"
filters:
"tag:Name": CI-TEST
register: vpcs

- name: Create VPC if it does not exist yet
amazon.aws.ec2_vpc_net:
name: "CI-TEST"
cidr_block: "10.0.0.0/24"
region: "{{ lookup('env', 'AWS_DEFAULT_REGION') }}"
state: present
when: vpcs.vpcs | length == 0
register: created_vpc

- name: Set VPC ID if new VPC created
ansible.builtin.set_fact:
private_key_path: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/id_rsa"

- name: "Generate local key pair {{ private_key_path }}"
community.crypto.openssh_keypair:
path: "{{ private_key_path }}"
type: rsa
size: 4096
regenerate: never
backend: cryptography
private_key_format: pkcs1
register: local_keypair

- name: Create the VM using testing-farm
ansible.builtin.command:
cmd: >-
testing-farm reserve
--compose {{ item.compose }}
--arch {{ item.arch | default("x86_64", true) }}
--duration {{ item.duration | default(30, true) }}
--ssh-public-key {{ private_key_path }}.pub
--no-autoconnect
register: tf_results
with_items: "{{ molecule_yml.platforms }}"
changed_when: true
async: 500
poll: 5

# - name: Testing output
# ansible.builtin.command:
# cmd: "echo '0acccef6-7ce6-4bb6-a0b4-3428762aa3ae [email protected]'"
# register: tf_results
# with_items: "{{ molecule_yml.platforms }}"
# changed_when: true

- name: Debug testing-farm results
ansible.builtin.debug:
msg: "{{ tf_results }}"
when: debug_outputs
vpc_id: "{{ created_vpc.vpc.id }}"
when: vpcs.vpcs | length == 0

- name: Initialize variable for parsed testing-farm results
- name: Set VPC ID to pre-existing VPC ID
ansible.builtin.set_fact:
tf_parsed_results: []
vpc_id: "{{ vpcs.vpcs[0].id }}"
when: vpcs.vpcs | length > 0

- name: Parse testing-farm result
ansible.builtin.include_tasks:
file: "tasks/_parse_tf_results.yml"
with_items: "{{ tf_results.results }}"
- name: Check if a subnet exists in the VPC
amazon.aws.ec2_vpc_subnet_info:
region: "{{ lookup('env', 'AWS_DEFAULT_REGION') }}"
filters:
vpc-id: "{{ vpc_id }}"
"tag:Name": CI-TEST
register: subnets

- name: Debug parsed testing-farm results
ansible.builtin.debug:
msg: "{{ tf_parsed_results }}"
when: debug_outputs
- name: Create subnet if it does not exist
amazon.aws.ec2_vpc_subnet:
vpc_id: "{{ vpc_id }}"
cidr: "10.0.0.0/24"
region: "{{ lookup('env', 'AWS_DEFAULT_REGION') }}"
tags:
Name: CI-TEST
state: present
when: subnets.subnets | length == 0
register: created_subnet

- name: Set new subnet ID if it did not exist
ansible.builtin.set_fact:
subnet_id: "{{ created_subnet.subnet.id }}"
when: subnets.subnets | length == 0

- name: Set subnet to pre-existing ID
ansible.builtin.set_fact:
subnet_id: "{{ subnets.subnets[0].id }}"
when: subnets.subnets | length > 0

# TODO: idempotency
- name: Populate instance config dict # noqa jinja
- name: Check if internet gateway exists
amazon.aws.ec2_vpc_igw_info:
region: "{{ lookup('env', 'AWS_DEFAULT_REGION') }}"
filters:
"tag:Name": "CI-TEST"
register: igw_facts

- name: Create internet gateway if it does not exist
amazon.aws.ec2_vpc_igw:
vpc_id: "{{ vpc_id }}"
region: "{{ lookup('env', 'AWS_DEFAULT_REGION') }}"
state: present
when: igw_facts.internet_gateways | length == 0
register: igw_exists

- name: Use pre-existing gateway ID if it exists
ansible.builtin.set_fact:
igw_id: "{{ igw_facts.internet_gateways[0].internet_gateway_id }}"
when: igw_facts.internet_gateways | length > 0

- name: use new gateway ID
set_fact:
igw_id: "{{ igw_exists.gateway_id }}"
when: igw_facts.internet_gateways | length == 0

- name: Gather route table facts
amazon.aws.ec2_vpc_route_table_info:
region: "{{ lookup('env', 'AWS_DEFAULT_REGION') }}"
filters:
"tag:Name": "CI-TEST"
vpc-id: "{{ vpc_id }}"
register: route_table

- name: Create route table if it does not exist
amazon.aws.ec2_vpc_route_table:
vpc_id: "{{ vpc_id }}"
region: "{{ lookup('env', 'AWS_DEFAULT_REGION') }}"
state: present
routes:
- dest: "0.0.0.0/0"
gateway_id: "{{ igw_id }}"
subnets:
- "{{ subnet_id }}"
tags:
Name: "CI-TEST"
when: route_table.route_tables | length == 0
register: created_route_table

- name: Set pre-existing route table ID
set_fact:
route_table_id: "{{ route_table.route_tables[0].id }}"
when: route_table.route_tables | length > 0

- name: Set route table ID to new route
set_fact:
route_table_id: "{{ created_route_table.route_table.id }}"
when: route_table.route_tables | length == 0

- name: Check if a security group has been instantiated
amazon.aws.ec2_security_group_info:
region: "{{ lookup('env', 'AWS_DEFAULT_REGION') }}"
filters:
group-name: CI-TEST
register: security_group

- name: Create 'CI-TEST' security group if it does not exist
amazon.aws.ec2_security_group:
name: "CI-TEST"
description: "Security group for CI testing"
vpc_id: "{{ vpc_id }}"
region: "{{ lookup('env', 'AWS_DEFAULT_REGION') }}"
rules:
- proto: tcp
ports: 22
cidr_ip: 0.0.0.0./0
- proto: tcp
ports: 80
cidr_ip: 0.0.0.0/0
- proto: tcp
ports: 443
cidr_ip: 0.0.0.0/0
- proto: tcp
ports: 5556
cidr_ip: 0.0.0.0/0
rule_desc: Allow traffic for using Dex as OIDC server + IDP
state: present
when: security_group.security_groups | length == 0
register: created_security_group

- name: Setting security group ID if it existed
ansible.builtin.set_fact:
sgID: "{{ security_group.security_groups[0]['group_id'] }}"
when: security_group.security_groups | length > 0

- name: Setting security group ID to new group
ansible.builtin.set_fact:
sgID: "{{ created_security_group.group_id }}"
when: security_group.security_groups | length == 0

- name: Generate instance ID
ansible.builtin.set_fact:
instance_id: "{{ lookup('pipe', 'uuidgen') | regex_replace('[^a-zA-Z0-9]', '') | truncate(8) }}"

- name: Launch EC2 instance
amazon.aws.ec2_instance:
name: "{{ 'CI-' + (lookup('env', 'GITHUB_RUN_ID') if lookup('env', 'GITHUB_RUN_ID') | length > 0 else instance_id) }}"
key_name: "{{ item.aws_key_name }}"
instance_type: "{{ item.instance_type }}"
image_id: "{{ item.image_id }}"
region: "{{ lookup('env', 'AWS_DEFAULT_REGION') }}"
vpc_subnet_id: "{{ subnet_id }}"
network:
assign_public_ip: true
security_group: "{{ sgID }}"
wait: yes
state: running
with_items: "{{ molecule_yml.platforms }}"
register: ec2_instance

- name: Set instance ID and IP
ansible.builtin.set_fact:
instanceID: "{{ ec2_instance.results[0].instances[0].instance_id }}"
instanceIP: "{{ ec2_instance.results[0].instances[0].network_interfaces[0].association.public_ip }}"

- name: Populate instance config dict
ansible.builtin.set_fact:
instance_conf_dict: {
'instance': "{{ item.uuid }}",
'address': "{{ item.ip }}",
# TODO: testing-farm is giving us root by default, ideally we would get non-root to get conditions closer to prod environments
'user': "{{ item.username }}",
'port': "22", # TODO: get it dynamically
'identity_file': "{{ private_key_path }}",
'instance': "{{ instanceID }}",
'address': "{{ instanceIP }}",
'user': "ec2-user",
'port': "22",
'identity_file': "{{ item.aws_key_name }}",
}
with_items: "{{ tf_parsed_results }}"
with_items: "{{ molecule_yml.platforms }}"
register: instance_config_dict

- name: Convert instance config dict to a list
ansible.builtin.set_fact:
instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}"

- name: Debug instance_conf
ansible.builtin.debug:
msg: "{{ instance_conf }}"
when: debug_outputs

- name: Dump instance config
ansible.builtin.copy:
content: |
Expand All @@ -111,13 +228,20 @@
children:
molecule:
hosts:
"{{ item.address }}":
ansible_user: {{ item.user }}
ansible_ssh_private_key_file: {{ private_key_path }}
"{{ instanceIP }}":
ansible_user: ec2-user
ansible_ssh_private_key_file: "{{ item.private_key_file_path }}"
ansible_become: true
ansible.builtin.set_fact:
molecule_inventory: >
{{ molecule_inventory | combine(inventory_partial_yaml | from_yaml, recursive=true) }}
loop: "{{ instance_conf }}"
with_items: "{{ molecule_yml.platforms }}"

- name: Add AWS EC2 Instance to known hosts
ansible.builtin.shell:
cmd: ssh-keyscan -H {{ instanceIP }} >> ~/.ssh/known_hosts
retries: 5
delay: 5

- name: Dump molecule_inventory
ansible.builtin.copy:
Expand All @@ -134,4 +258,4 @@
that: "'molecule' in groups"
fail_msg: |
molecule group was not found inside inventory groups: {{ groups }}
run_once: true # noqa: run-once[task]
run_once: true
Loading

0 comments on commit 9d406c2

Please sign in to comment.