Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
cb7e5a6
Setup GH Action to create VMs on the cloud
Ziggiyzoo Jan 23, 2026
f5292b0
Add clouds.yaml
Ziggiyzoo Jan 23, 2026
946453d
Fix workflow dispatch
Ziggiyzoo Jan 23, 2026
01dcf7b
Move env of second job
Ziggiyzoo Jan 23, 2026
ea8d104
Change to on push for testing?
Ziggiyzoo Jan 23, 2026
e177601
hashicorp
Ziggiyzoo Jan 23, 2026
21cd4b8
Create clouds.yaml after checkout
Ziggiyzoo Jan 23, 2026
0d62df9
add quotes around echo
Ziggiyzoo Jan 23, 2026
d09b630
Correct auto approve
Ziggiyzoo Jan 23, 2026
3826225
Change key-pair name
Ziggiyzoo Jan 23, 2026
a64606d
change where workdir is set
Ziggiyzoo Jan 23, 2026
054bfaa
change where workdir is set
Ziggiyzoo Jan 23, 2026
15d3bba
Have a nap
Ziggiyzoo Jan 23, 2026
72f0535
Add env. to env echo
Ziggiyzoo Jan 23, 2026
333c959
Attempt to run plan and apply without showing diff
Ziggiyzoo Jan 23, 2026
740c130
Attempt to run plan and apply without showing diff
Ziggiyzoo Jan 23, 2026
6650e45
terraform destroy cannot be called on a tfplan
Ziggiyzoo Jan 23, 2026
c67e984
Add step ID
Ziggiyzoo Jan 23, 2026
7ed7b33
Change how output is set
Ziggiyzoo Jan 23, 2026
8b7e8c1
Use ''
Ziggiyzoo Jan 23, 2026
3144c69
Correct command
Ziggiyzoo Jan 23, 2026
f0b5ec9
Re arrange command
Ziggiyzoo Jan 23, 2026
85647e3
Re arrange command
Ziggiyzoo Jan 23, 2026
8744fef
Test output
Ziggiyzoo Jan 23, 2026
f56eb8a
Test output
Ziggiyzoo Jan 23, 2026
ed6c693
Test output
Ziggiyzoo Jan 23, 2026
f285d73
Test output
Ziggiyzoo Jan 23, 2026
6e43e94
Functional creating of VMs with GH Actions
Ziggiyzoo Jan 23, 2026
2feb463
Resturcture
Ziggiyzoo Jan 26, 2026
b845c09
Create image with packer
Ziggiyzoo Jan 27, 2026
e9af6b6
Functioning local packer image build
Ziggiyzoo Jan 29, 2026
7f672b8
Run on push
Ziggiyzoo Jan 29, 2026
885af67
Fix indentation
Ziggiyzoo Jan 29, 2026
6fd1720
Remove on workflow dispatch
Ziggiyzoo Jan 29, 2026
4440ce0
Correct clouds.yaml env
Ziggiyzoo Jan 29, 2026
112972e
Use variable file
Ziggiyzoo Jan 29, 2026
f6ae63f
Network needs to be a list?
Ziggiyzoo Jan 29, 2026
aba21aa
Network needs to be a list?
Ziggiyzoo Jan 29, 2026
dea3dd1
Add values to env
Ziggiyzoo Jan 29, 2026
2d37641
Test
Ziggiyzoo Jan 29, 2026
bbd8c4d
Test
Ziggiyzoo Jan 29, 2026
9fac3d6
Test
Ziggiyzoo Jan 29, 2026
6d619cd
Test
Ziggiyzoo Jan 29, 2026
31a3e0f
Test
Ziggiyzoo Jan 29, 2026
568c4b3
Test
Ziggiyzoo Jan 29, 2026
94246bb
Test installing openstack cli
Ziggiyzoo Jan 29, 2026
f9468f7
Test installing openstack cli
Ziggiyzoo Jan 29, 2026
0d5f5bd
Change variable types
Ziggiyzoo Jan 29, 2026
7f49daf
Change variable types
Ziggiyzoo Jan 29, 2026
bf04900
Change variable types
Ziggiyzoo Jan 29, 2026
10e7d42
Change variable types
Ziggiyzoo Jan 29, 2026
adcd4a0
Test with new command to get base image ID
Ziggiyzoo Jan 29, 2026
15a4b44
Try adding floating IP to packer build VM
Ziggiyzoo Jan 30, 2026
b159911
Add to internal network
Ziggiyzoo Jan 30, 2026
45ed949
Create image
Ziggiyzoo Feb 10, 2026
1bd7565
Helps if you actually use the FIP..
Ziggiyzoo Feb 10, 2026
74ef21c
Floating IP is actually Floating IP ID
Ziggiyzoo Feb 10, 2026
0ecc951
Correct env assingments
Ziggiyzoo Feb 10, 2026
0807593
Change name of VM
Ziggiyzoo Feb 10, 2026
5dd9ad1
Test ssh interaface
Ziggiyzoo Feb 10, 2026
36369a5
Remove ssh interface
Ziggiyzoo Feb 10, 2026
f3f69b3
Functional manual deployment
Ziggiyzoo Feb 16, 2026
30a22b7
Update naming of runners in registration, rename workflows
Ziggiyzoo Feb 17, 2026
0162155
Adjust workflow to use vault
Ziggiyzoo Feb 17, 2026
d6cc182
Add destroy workflow
Ziggiyzoo Feb 17, 2026
ffff7ea
Re add hosts.ini to ignore
Ziggiyzoo Feb 17, 2026
965555a
Functioning manual deployment
Ziggiyzoo Feb 17, 2026
f94f5d0
Working manual Deletion & Creation of runners
Ziggiyzoo Feb 17, 2026
f8213ee
Add VM IPs to ansible hosts on destroy correctly
Ziggiyzoo Feb 17, 2026
a97a7d0
Run on dispatch
Ziggiyzoo Feb 18, 2026
7abdf48
Address GH Workflow comments
Ziggiyzoo Feb 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions .github/workflows/create-gh-action-runners.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
name: Create Github Action Runners
on:
workflow-dispatch:
inputs:
runner_count:
description: "Number of Runners to Deploy"
required: true
default: 5

runner_flavor:
description: "The Flavor of VM to create. l3.nano (More Runners, Less Power) | l3.micro (Middle Ground) | l3.tiny (Less Runners, More Power)"
required: true
default: l3.nano

jobs:
apply-terraform:
runs-on: ubuntu-latest
defaults:
run:
working-directory: self-hosted-gh-runners/static/terraform
outputs:
vm_ips: ${{ steps.check-vms.outputs.vm-ips }}
env:
OS_CLIENT_CONFIG_FILE: ${{ github.workspace }}/clouds.yaml
IMAGE_NAME: github-action-runner-image
FLAVOR_NAME: l3.nano
TF_VAR_runner_count: ${{ github.events.inputs.runner_count }}
TF_VAR_flavor_name: ${{ github.events.inputs.runner_flavor }}
steps:
- name: Checkout repo
uses: actions/checkout@v6

- name: Create clouds.yaml
run: echo "${{ secrets.OPENSTACK_CLOUDS }}" >> ${{ github.workspace }}/clouds.yaml

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.14.3
terraform_wrapper: false

- name: Get terraform.tfstate
run: |
vault login ${{ secrets.VAULT_TOKEN }}
vault kv get -address="https://secrets.isis.rl.ac.uk" -mount="fase-ci-cd" -field="tfstate" "tfstate" > terraform.tfstate

- name: Terraform init
run: terraform init

- name: Terraform plan
run: terraform plan -out=tfplan 1> /dev/null

- name: Terraform apply
run: terraform apply -auto-approve tfplan 1> /dev/null

- name: Create hosts.ini & Crete list of IPs
id: check-vms
run: |
echo $(terraform output -json vm_ips) | jq -r '.[]' >> ../ansible/hosts.ini \
&& echo "vm_ips=$(echo $(terraform output -json vm_ips) | jq -r '.[]')" >> $GITHUB_OUTPUT

- name: Put terraform.tfstate
run: |
vault login ${{ secrets.VAULT_TOKEN }}
vault kv put -address="https://secrets.isis.rl.ac.uk" -mount="fase-ci-cd" "tfstate" tfstate="@terraform.tfstate"

- name: Setup SSH
env:
VM_IPS: ${{ steps.export.outputs.check-vms.vm_ips}}
FASE_SSH_KEY: ${{ secrets.FASE_SSH_KEY}}
run: |
mkdir -p ~/.ssh/
echo -e "$FASE_SSH_KEY" > ~/.ssh/id_rsa
chmod 400 ~/.ssh/id_rsa
eval $(ssh-agent -s)
ssh-add ~/.ssh/id_rsa
echo "$VM_IPS" >> ../ansible/hosts.txt
sed -i '$!s/$/,/' ../ansible/hosts.txt
ssh-keyscan -f ../ansible/hosts.txt >> ~/.ssh/known_hosts
Comment thread
Ziggiyzoo marked this conversation as resolved.

- name: Configure the Runners
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
run: |
cat > /tmp/extra_vars.yml << EOF
ACCESS_TOKEN: "$ACCESS_TOKEN"
ORG: "${{ github.repository_owner }}"
EOF

ansible-playbook \
--private-key ~/.ssh/id_rsa \
-u ${{ secrets.ANSIBLE_DEPLOY_USER }} \
-i ../ansible/hosts.ini \
../ansible/create-gh-action-runners.yaml \
--extra-vars "@/tmp/extra_vars.yml"

rm -f /tmp/extra_vars.yml

- name: Put hosts.ini
env:
VM_IPS: ${{ steps.export.outputs.check-vms.vm_ips}}
run: |
vault login ${{ secrets.VAULT_TOKEN }}
vault kv put -address="https://secrets.isis.rl.ac.uk" -mount="fase-ci-cd" "hosts" hosts="$VM_IPS"



82 changes: 82 additions & 0 deletions .github/workflows/create-gh-runner-image.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: create-gh-action-runner-image.yaml
on:
workflow-dispatch:

jobs:
packer:
runs-on: ubuntu-latest
defaults:
run:
working-directory: self-hosted-gh-runners/static/packer
env:
OS_CLOUD: openstack
IMAGE_NAME: gh-action-runner-image
BASE_IMAGE_NAME: ubuntu-noble-24.04-nogui
FLAVOR_NAME: l3.nano
NETWORK_NAME: ua-ci-cd
FLOATING_IP: ${{ secrets.PACKER_FLOATING_IP }}
OS_CLIENT_CONFIG_FILE: ${{ github.workspace }}/clouds.yaml
GITHUB_ORG: ${{ github.repository_owner }}

outputs:
base_image_id: ${{ steps.export.outputs.base_image_id }}
flavor_id: ${{ steps.export.outputs.flavor_id }}
network_id: ${{ steps.export.outputs.network_id }}
floating_ip_id: ${{ steps.export.outputs.floating_ip_id }}
runner_version: ${{ steps.get-runner-version.outputs.runner-version }}

steps:
- name: Checkout repo
uses: actions/checkout@v6

- name: Install OpenStack CLI
run: |
sudo apt update
sudo apt install -y python3-pip
pip3 install python-openstackclient

- name: Create clouds.yaml
run: echo "${{ secrets.OPENSTACK_CLOUDS }}" >> ${{ github.workspace }}/clouds.yaml

- name: Export Base Image, Build VM Flavor, Network & Floating IP IDs
id: export
run: |
echo "Export Base Image ID"
echo "base_image_id=$(openstack image show ${{ env.BASE_IMAGE_NAME }} -f value -c id)" >> "$GITHUB_OUTPUT"

echo "Export Build VM Flavor ID"
echo "flavor_id=$(openstack flavor show ${{ env.FLAVOR_NAME }} -f value -c id)" >> "$GITHUB_OUTPUT"

echo "Export Network ID"
echo "network_id=$(openstack network show ${{ env.NETWORK_NAME }} -f value -c id)" >> "$GITHUB_OUTPUT"

echo "Export Floating IP ID"
echo "floating_ip_id=$(openstack floating ip show ${{ env.FLOATING_IP }} -f value -c id)" >> "$GITHUB_OUTPUT"

- name: Get Latest Runner version
id: get-runner-version
run: |
echo "Get Latest Runner Version"
echo "runner_version=$(curl -s https://api.github.com/repos/actions/runner/releases/latest | grep '"tag_name":' | sed -E 's/.*"v?([^"]+)".*/\1/')" >> "$GITHUB_OUTPUT"

- name: Setup packer
uses: hashicorp/setup-packer@main
with:
version: latest

- name: Packer Init
run: packer init .

- name: Packer Validate & Build
env:
BASE_IMAGE_ID: ${{ steps.export.outputs.base_image_id }}
FLAVOR_ID: ${{ steps.export.outputs.flavor_id }}
NETWORK_ID: ${{ steps.export.outputs.network_id }}
FLOATING_IP_ID: ${{ steps.export.outputs.floating_ip_id }}
RUNNER_VERSION: ${{ steps.get-runner-version.outputs.runner_version }}
run: |
packer validate .
packer build .

- name: Delete old images
run: ./scripts/delete_old_images.sh
80 changes: 80 additions & 0 deletions .github/workflows/destroy-gh-action-runners.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: apply-sh-gh-runner-config
on:
workflow-dispatch:

jobs:
apply-terraform:
runs-on: ubuntu-latest
defaults:
run:
working-directory: self-hosted-gh-runners/static/terraform
outputs:
vm_ips: ${{ steps.check-vms.outputs.vm-ips }}
env:
OS_CLIENT_CONFIG_FILE: ${{ github.workspace }}/clouds.yaml
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
steps:
- name: Checkout repo
uses: actions/checkout@v6

- name: Create clouds.yaml
run: echo "${{ secrets.OPENSTACK_CLOUDS }}" >> ${{ github.workspace }}/clouds.yaml

- name: Get terraform.tfstate
run: |
vault login ${{ secrets.VAULT_TOKEN }}
vault kv get -address="https://secrets.isis.rl.ac.uk" -mount="fase-ci-cd" -field="hosts" "hosts" >> ../ansible/hosts.txt

- name: Setup SSH
env:
VM_IPS: ${{ steps.export.outputs.check-vms.vm_ips}}
FASE_SSH_KEY: ${{ secrets.FASE_SSH_KEY}}
run: |
mkdir -p ~/.ssh/
echo -e "$FASE_SSH_KEY" > ~/.ssh/id_rsa
chmod 400 ~/.ssh/id_rsa
eval $(ssh-agent -s)
ssh-add ~/.ssh/id_rsa
echo "$VM_IPS" >> ../ansible/hosts.txt
sed -i '$!s/$/,/' ../ansible/hosts.txt
ssh-keyscan -f ../ansible/hosts.txt >> ~/.ssh/known_hosts

Comment thread
Ziggiyzoo marked this conversation as resolved.
- name: Run GitHub runner setup playbook
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
run: |
cat > /tmp/extra_vars.yml << EOF
ACCESS_TOKEN: "$ACCESS_TOKEN"
ORG: "${{ github.repository_owner }}"
EOF

ansible-playbook \
--private-key ~/.ssh/id_rsa \
-i ../ansible/hosts.ini \
../ansible/destroy-gh-action-runners.yaml \
--extra-vars "@/tmp/extra_vars.yml"

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.14.3
terraform_wrapper: false

- name: Get terraform.tfstate
run: |
vault login ${{ secrets.VAULT_TOKEN }}
vault kv get -address="https://secrets.isis.rl.ac.uk" -mount="fase-ci-cd" -field="tfstate" "tfstate" > terraform.tfstate

- name: Terraform init
run: terraform init

- name: Terraform destroy
run: terraform destroy -auto-approve 1> /dev/null

- name: Put terraform.tfstate
run: |
vault login ${{ secrets.VAULT_TOKEN }}
vault kv put -address="https://secrets.isis.rl.ac.uk" -mount="fase-ci-cd" "tfstate" tfstate="@terraform.tfstate"



9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Ignore any OpenStack cloud application creds
clouds.yaml

# Ignore any Terraform State / Init
terraform.*
*.terraform*

# Ignore Ansible hosts.ini
hosts.ini
7 changes: 7 additions & 0 deletions self-hosted-gh-runners/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,10 @@ GitHub workflow_job webhook can trigger creation & destruction of VMs & runners
6. remove runner
7. terraform removes VM

## Steps to implement

1. Recreate the static deployment done via jenkins
2. Test it, of course!

3. Create a service to listen to workflow_job webook.
4. Set the service to trigger terraform changes
47 changes: 47 additions & 0 deletions self-hosted-gh-runners/runner-image/packer/image.pkr.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
packer {
required_plugins {
openstack = {
version = "~> 1"
source = "github.com/hashicorp/openstack"
}
ansible = {
version = "~> 1.1.4"
source = "github.com/hashicorp/ansible"
}
}
}

source "openstack" "gh_action_runner_image" {
ssh_username = "ubuntu"
cloud = "openstack"
source_image = var.source_image
flavor = var.flavor
networks = [var.network]
floating_ip = var.floating_ip
image_name = var.new_image
image_visibility = "private"
metadata = {
built_by = "packer"
team = "ua-ci-cd"
usage = "Self Hosted GH Action Runners"
}
}

build {
name = "openstack-image-build"
sources = ["source.openstack.gh_action_runner_image"]

provisioner "file" {
sources = ["scripts/configure.sh", "scripts/docker_prune.sh", "scripts/job_started.sh", "scripts/job_completed.sh", "scripts/remove.sh"]
destination = "~/"
}

provisioner "shell" {
script = "scripts/setup_vm.sh"
}

provisioner "ansible-local" {
playbook_file = "playbooks/install_dependencies.yaml"
extra_arguments = ["--extra-vars", "RUNNER_VERSION=${var.runner_version}"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
- name: Install dependencies
remote_user: ubuntu
hosts: 127.0.0.1
connection: local

tasks:
- name: Install docker
become: true
ansible.builtin.apt:
name: docker.io
state: present

- name: Install docker buildkit features
become: true
ansible.builtin.apt:
name: docker-buildx
state: present

- name: Install GitHub CLI
become: true
ansible.builtin.apt:
name: gh
state: present

- name: Setup docker user
become: true
ansible.builtin.user:
name: ubuntu
groups: docker
append: yes

- name: Extract Runner
ansible.builtin.unarchive:
src: https://github.com/actions/runner/releases/download/v{{RUNNER_VERSION}}/actions-runner-linux-x64-{{RUNNER_VERSION}}.tar.gz
dest: ~/actions-runner
remote_src: yes

- name: Install dependencies
ansible.builtin.shell:
chdir: ~/actions-runner
cmd: sudo ./bin/installdependencies.sh
Loading