Skip to content
This repository was archived by the owner on Nov 4, 2025. It is now read-only.

Commit 8b4e284

Browse files
authored
feat: AS-314 Docker Builds And Deploys (#2)
1 parent 081b0b8 commit 8b4e284

File tree

13 files changed

+284
-18
lines changed

13 files changed

+284
-18
lines changed

.github/workflows/build-and-publish.yml

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,71 @@ on:
44
push:
55
branches:
66
- "main"
7+
paths:
8+
- 'voxelbot/**'
9+
- 'ansible/**'
10+
- 'dockerfile'
11+
- 'docker-compose.yaml'
12+
- '.github/workflows/build-and-publish.yml'
13+
14+
concurrency:
15+
group: "${{ github.ref_name }}-build-and-deploy"
716

817
jobs:
918
build:
1019
runs-on: ubuntu-latest
1120
permissions:
1221
contents: 'write'
1322
id-token: 'write'
14-
env:
15-
GCP_LOCATION: ''
16-
GCP_PROJECT: ''
17-
GCP_DOCKER_REPOSITORY: ''
18-
GCP_HELM_REGISTRY: ''
19-
GCP_SERVICE_ACCOUNT: ''
20-
VERSION: ${{ github.sha }}
23+
2124
steps:
2225
- uses: actions/checkout@v4
26+
2327
- name: Authenticate to Google Cloud
2428
uses: google-github-actions/auth@v2
2529
with:
26-
project_id: ${{ env.GCP_PROJECT }}
27-
service_account: ${{ env.GCP_SERVICE_ACCOUNT }}
30+
project_id: ${{ secrets.GCP_PROJECT }}
31+
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
2832
workload_identity_provider: ${{ secrets.ORG_GOOGLE_WORKLOAD_IDP }}
33+
2934
- name: Set Up Cloud SDK
3035
uses: google-github-actions/setup-gcloud@v2
36+
with:
37+
install_components: 'beta'
38+
3139
- name: Docker login
3240
run: |
3341
gcloud auth print-access-token | docker login \
3442
-u oauth2accesstoken \
35-
--password-stdin "https://${{ env.GCP_LOCATION }}-docker.pkg.dev"
36-
- name: Helm login
37-
run: |
38-
gcloud auth print-access-token | \
39-
helm registry login -u oauth2accesstoken \
40-
--password-stdin "https://${{ env.GCP_LOCATION }}-docker.pkg.dev"
43+
--password-stdin "https://${{ secrets.GCP_LOCATION }}-docker.pkg.dev"
44+
4145
- name: Set up Docker Buildx
4246
uses: docker/setup-buildx-action@v3
47+
4348
- name: Build and push
4449
uses: docker/build-push-action@v6
4550
with:
4651
push: true
4752
platforms: linux/amd64,linux/arm64
48-
file: ./external/VoxelBot/dockerfile
49-
context: ./external/VoxelBot
50-
tags: ${{ env.GCP_LOCATION }}-docker.pkg.dev/${{ env.GCP_PROJECT }}/${{ env.GCP_DOCKER_REPOSITORY }}/voxel51-discordbot:${{ env.VERSION }}
53+
file: dockerfile
54+
context: .
55+
tags: ${{ secrets.GCP_LOCATION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT }}/${{ secrets.GCP_DOCKER_REPOSITORY }}/voxel51-discordbot:${{ github.sha }}
56+
cache-from: type=gha
57+
cache-to: type=gha,node=max
58+
59+
- name: Deploy via ansible
60+
shell: bash
61+
env:
62+
DOCKER_REGISTRY: "${{ secrets.GCP_LOCATION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT }}/${{ secrets.GCP_DOCKER_REPOSITORY }}/"
63+
GCP_SM_KEY: "${{ secrets.GCP_SM_KEY }}"
64+
TAG: ${{ github.sha }}
65+
GCP_COMPUTE_SERVER_NAME: "${{ secrets.GCP_COMPUTE_SERVER_NAME }}"
66+
GCP_LOCATION: ${{ secrets.GCP_LOCATION }}
67+
GCP_PROJECT: ${{ secrets.GCP_PROJECT }}
68+
run: |
69+
pushd ansible
70+
yq -i ".projects |= [\"$GCP_PROJECT\"]" ./inventory/gcp.yml
71+
yq -i ".zones |= [\"$GCP_LOCATION\"]" ./inventory/gcp.yml
72+
sudo pipx inject ansible-core -r requirements.txt
73+
ansible-playbook site.yml
74+
popd

ansible/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Ansible Automation For Docker Compose Systems
2+
3+
In order to provide a GitOps
4+
flow for docker compose, we wrote a suite of ansible tooling
5+
to be triggered via GitHub actions.
6+
7+
This automation includes a few task sets:
8+
9+
1. A task set to log in to GCP and internal docker registries
10+
1. A task set to deploy our docker compose stacks. This includes:
11+
1. Ensuring there is a `.env` file either in google secrets manager
12+
or on disk for the stack to use.
13+
1. Ensuring the ansible user is part of the `docker` linux group.
14+
1. Bringing up the docker compose stack
15+
16+
## Variables
17+
18+
Host variables are documented and defaulted in [this](group_vars/all.yaml) file.
19+
20+
## Running
21+
22+
You can run locally via the `ansible-playbook` command.
23+
You can set your hosts via the command line via the `GCP_COMPUTE_SERVER_NAME`
24+
environment variable.
25+
26+
A list of environment variables:
27+
28+
* `DOCKER_REGISTRY` - The registry to pull images from
29+
* `GCP_COMPUTE_SERVER_NAME` - The ansbile host or group to deploy to
30+
* `GCP_LOCATION` - The GCP location of the registry
31+
* `GCP_SM_KEY` - The GCP secret with .env file contents
32+
* `TAG` - The image tag to deploy
33+
34+
An example:
35+
36+
```shell
37+
export DOCKER_REGISTRY="us.gcr.io/.../..."
38+
export GCP_COMPUTE_SERVER_NAME=some-server-name
39+
export GCP_LOCATION=some-gcp-location
40+
export GCP_SM_KEY="some-key-name"
41+
export TAG="abc123"
42+
ansible-playbook main.yml
43+
```

ansible/ansible.cfg

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[inventory]
2+
enable_plugins = gcp_compute
3+
4+
[defaults]
5+
inventory = inventory/gcp.yml
6+
7+
[ssh_connection]
8+
# Enabling pipelining reduces the number of SSH operations required
9+
# to execute a module on the remote server.
10+
# This can result in a significant performance improvement
11+
# when enabled.
12+
pipelining = True
13+
ssh_executable = scripts/gcp-ssh-wrapper.sh
14+
ssh_args = None
15+
scp_if_ssh = True
16+
scp_executable = scripts/gcp-scp-wrapper.sh

ansible/group_vars/all.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
ansible_ssh_args: --tunnel-through-iap --zone={{ zone }} --project={{ project }} --no-user-output-enabled --quiet
3+
ansible_scp_extra_args: --tunnel-through-iap --zone={{ zone }} --quiet
4+
docker_dir: /deploy/voxel51-discordbot
5+
compose_async_timeout: 30
6+
gcp_sm_key: "{{ lookup('env', 'GCP_SM_KEY') }}"
7+
docker_registry: "{{ lookup('env', 'DOCKER_REGISTRY') }}"
8+
tag: "{{ lookup('env', 'TAG') }}"

ansible/inventory/gcp.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
plugin: google.cloud.gcp_compute
2+
projects: []
3+
zones: []
4+
filters:
5+
- status = RUNNING
6+
auth_kind: application
7+
hostnames:
8+
- name

ansible/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
google-auth==2.35.0
2+
requests==2.31.0

ansible/scripts/gcp-scp-wrapper.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/bash
2+
# This is a wrapper script allowing to use GCP's IAP option to connect
3+
# to our servers.
4+
5+
# Ansible passes a large number of SSH parameters along with the hostname as the
6+
# second to last argument and the command as the last. We will pop the last two
7+
# arguments off of the list and then pass all of the other SSH flags through
8+
# without modification:
9+
host="${@: -2: 1}"
10+
cmd="${@: -1: 1}"
11+
12+
# Unfortunately ansible has hardcoded scp options, so we need to filter these out
13+
# It's an ugly hack, but for now we'll only accept the options starting with '--'
14+
declare -a opts
15+
for scp_arg in "${@: 1: $# -3}" ; do
16+
if [[ "${scp_arg}" == --* ]] ; then
17+
opts+="${scp_arg} "
18+
fi
19+
done
20+
21+
# Remove [] around our host, as gcloud scp doesn't understand this syntax
22+
cmd=`echo "${cmd}" | tr -d []`
23+
24+
exec gcloud beta compute scp $opts "${host}" "${cmd}"

ansible/scripts/gcp-ssh-wrapper.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
# This is a wrapper script allowing to use GCP's IAP SSH option to connect
3+
# to our servers.
4+
5+
# Ansible passes a large number of SSH parameters along with the hostname as the
6+
# second to last argument and the command as the last. We will pop the last two
7+
# arguments off of the list and then pass all of the other SSH flags through
8+
# without modification:
9+
host="${@: -2: 1}"
10+
cmd="${@: -1: 1}"
11+
12+
# Unfortunately ansible has hardcoded ssh options, so we need to filter these out
13+
# It's an ugly hack, but for now we'll only accept the options starting with '--'
14+
declare -a opts
15+
for ssh_arg in "${@: 1: $# -3}" ; do
16+
if [[ "${ssh_arg}" == --* ]] ; then
17+
opts+="${ssh_arg} "
18+
fi
19+
done
20+
21+
exec gcloud beta compute ssh $opts "${host}" -- -C "${cmd}"

ansible/site.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
- name: Deploy Voxel51 Discord Bot
3+
hosts: "{{ lookup('env', 'GCP_COMPUTE_SERVER_NAME') }}"
4+
gather_facts: true
5+
6+
tasks:
7+
- name: Sync User Permissions
8+
include_tasks: tasks/users.yml
9+
10+
- name: Ensure paths and dot envs
11+
include_tasks: tasks/fs.yml
12+
13+
- name: Deploy stack
14+
include_tasks: tasks/docker.yml

ansible/tasks/docker.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
- name: Docker login
3+
shell: |
4+
gcloud auth configure-docker \
5+
"{{ lookup('env', 'GCP_LOCATION') }}-docker.pkg.dev" \
6+
--quiet
7+
8+
- name: Pull docker images
9+
community.docker.docker_compose_v2_pull:
10+
project_src: "{{ docker_dir }}"
11+
environment:
12+
DOCKER_REGISTRY: "{{ docker_registry }}"
13+
TAG: "{{ tag }}"
14+
15+
- name: Create and start services
16+
community.docker.docker_compose_v2:
17+
project_src: "{{ docker_dir }}"
18+
build: "never"
19+
environment:
20+
DOCKER_REGISTRY: "{{ docker_registry }}"
21+
TAG: "{{ tag }}"
22+
register: compose_out
23+
24+
# Assert that states == running here
25+
- name: Verify all services are running
26+
ansible.builtin.assert:
27+
that:
28+
- item.State == 'running'
29+
msg: "{{ item.Name }} failed to start properly"
30+
quiet: true
31+
with_items: "{{ compose_out.containers }}"
32+
loop_control:
33+
label: "{{ item.Name }}"

0 commit comments

Comments
 (0)