Skip to content

Commit aa2d61a

Browse files
committed
Deployment fix for dev deployment through hosted/<branch> PRs
1 parent 91118c9 commit aa2d61a

File tree

6 files changed

+306
-3
lines changed

6 files changed

+306
-3
lines changed

.github/assets/dev-taskdef.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@
5353
"networkMode": "awsvpc",
5454
"memory": "1024",
5555
"cpu": "512",
56-
"executionRoleArn": "arn:aws:iam::605436358845:role/docs-dev-TaskRole",
56+
"executionRoleArn": "arn:aws:iam::058264511034:role/docs-dev-TaskRole",
5757
"family": "docs-dev-taskdefinition",
58-
"taskRoleArn": "arn:aws:iam::605436358845:role/docs-dev-TaskRole",
58+
"taskRoleArn": "arn:aws:iam::058264511034:role/docs-dev-TaskRole",
5959
"runtimePlatform": {
6060
"operatingSystemFamily": "LINUX"
6161
},
@@ -81,7 +81,7 @@
8181
},
8282
{
8383
"key": "IAC",
84-
"value": "terraform-workspace-aws-dev-applications-eu-west-1-apps-docs-dev-polygon-technology"
84+
"value": "terraform-workspace-aws-dev-apps-eu-west-1-apps-docs-dev-polygon-technology"
8585
},
8686
{
8787
"key": "Team",
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
on:
2+
workflow_call:
3+
inputs:
4+
environment:
5+
required: false
6+
type: string
7+
default: "dev"
8+
core_app:
9+
required: false
10+
type: string
11+
description: "Core app name"
12+
default: "docs"
13+
account_number:
14+
required: false
15+
type: string
16+
description: "AWS Account number for deployment"
17+
default: "058264511034"
18+
region:
19+
required: false
20+
type: string
21+
description: "AWS region for deployment"
22+
default: "eu-west-1"
23+
task_definition:
24+
required: false
25+
type: string
26+
description: "Task Definition path for deployment"
27+
default: ".github/assets/dev-taskdef.json"
28+
cluster_name:
29+
required: false
30+
type: string
31+
description: "Cluster name for deployment"
32+
default: "frontend-dev-ecs-cluster"
33+
34+
jobs:
35+
build_site_data:
36+
name: ${{ inputs.environment }} deployment
37+
runs-on: ubuntu-latest
38+
environment: ${{ inputs.environment }}
39+
permissions:
40+
id-token: write
41+
contents: write
42+
env:
43+
AWS_REGION: ${{ inputs.region }}
44+
ECR_REPOSITORY: ${{ inputs.core_app }}-${{ inputs.environment }}-ecr
45+
ECS_SERVICE: ${{ inputs.core_app }}-${{ inputs.environment }}-ecs-service
46+
ECS_CLUSTER: frontend-${{ inputs.environment }}-ecs-cluster
47+
ECS_TASK_DEFINITION: ${{ inputs.task_definition }}
48+
APP_NAME: ${{ inputs.core_app }}-${{ inputs.environment }}
49+
steps:
50+
- name: Checkout Code Repository
51+
uses: actions/checkout@v3
52+
with:
53+
fetch-depth: 0
54+
55+
- name: Configure AWS credentials
56+
uses: aws-actions/configure-aws-credentials@v4
57+
with:
58+
aws-region: ${{ env.AWS_REGION }}
59+
role-to-assume: arn:aws:iam::${{ inputs.account_number }}:role/${{ env.APP_NAME }}-GithubActionsRole
60+
role-session-name: GithubActionsSession
61+
62+
- uses: actions/setup-python@v4
63+
with:
64+
python-version: '3.11'
65+
66+
- name: Install pipenv
67+
run: pip install pipenv
68+
69+
- name: Install GitHub CLI
70+
run: |
71+
(type -p wget >/dev/null || (sudo apt update && sudo apt-get install wget -y)) \
72+
&& sudo mkdir -p -m 755 /etc/apt/keyrings \
73+
&& wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
74+
&& sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
75+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
76+
&& sudo apt update \
77+
&& sudo apt install gh -y
78+
79+
- name: Authenticate GitHub CLI
80+
run: gh auth login --with-token <<< "${{ secrets.GITHUB_TOKEN }}"
81+
82+
- name: Build Site
83+
run: |
84+
python build_branches.py
85+
86+
- name: Login to Amazon ECR
87+
id: login-ecr
88+
uses: aws-actions/amazon-ecr-login@v1
89+
90+
- name: Build, tag, and push image to Amazon ECR
91+
id: build-image
92+
env:
93+
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
94+
IMAGE_TAG: ${{ github.sha }}-${{ github.run_number }}
95+
ECR_REPOSITORY: ${{ env.APP_NAME }}-ecr
96+
run: |
97+
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfile.review .
98+
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
99+
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
100+
101+
- name: Fill in the new image ID in the Amazon ECS task definition
102+
id: task-def
103+
uses: aws-actions/amazon-ecs-render-task-definition@v1
104+
with:
105+
task-definition: ${{ env.ECS_TASK_DEFINITION }}
106+
container-name: ${{ env.APP_NAME }}
107+
image: ${{ steps.build-image.outputs.image }}
108+
109+
- name: Deploy Amazon ECS task definition
110+
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
111+
with:
112+
task-definition: ${{ steps.task-def.outputs.task-definition }}
113+
service: ${{ env.ECS_SERVICE }}
114+
cluster: ${{ env.ECS_CLUSTER }}
115+
wait-for-service-stability: true
116+
117+
- name: Cloudflare Cache Purge
118+
uses: nathanvaughn/actions-cloudflare-purge@master
119+
with:
120+
cf_zone: ${{ secrets.CLOUDFLARE_ZONE }}
121+
cf_auth: ${{ secrets.CLOUDFLARE_AUTH_KEY }}
122+
hosts: ${{ env.APP_NAME }}.polygon.technology
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: hosted branch pr deployment
2+
on:
3+
pull_request:
4+
types: [opened, edited, reopened]
5+
branches:
6+
- hosted/*
7+
push:
8+
branches:
9+
- dev
10+
workflow_dispatch:
11+
12+
jobs:
13+
deploy:
14+
uses: ./.github/workflows/build_and_deploy.yml
15+
secrets: inherit
16+

Dockerfile.review

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM nginx:alpine
2+
3+
COPY nginx.conf /etc/nginx/nginx.conf
4+
COPY app /app
5+
6+
WORKDIR /app
7+
EXPOSE 80
8+
CMD ["nginx", "-g", "daemon off;"]

build_branches.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import ast
2+
import os
3+
import shutil
4+
import subprocess
5+
6+
7+
def install_mkdocs_with_pipenv():
8+
"""
9+
Builds a particular branch site.
10+
Having a varying set of requirements can be handled by having each branch
11+
build their dependencies and then running mkdocs build.
12+
"""
13+
folder = os.getcwd()
14+
subprocess.run(["pipenv", "install", "--site-packages"], cwd=folder)
15+
subprocess.run(["pipenv", "install", "-r", "requirements.txt"], cwd=folder)
16+
subprocess.run(["pipenv", "run", "mkdocs", "build"], cwd=folder)
17+
18+
def copy_folder(source_dir, target_dir):
19+
"""
20+
Copies contents from source directory to target directory
21+
:param source_dir: Source directory from which contents are to be copied
22+
:param target_dir: Target Directory where the contents are copied to.
23+
"""
24+
os.makedirs(target_dir, exist_ok=True)
25+
26+
for item in os.listdir(source_dir):
27+
source_path = os.path.join(source_dir, item)
28+
target_path = os.path.join(target_dir, item)
29+
30+
if os.path.isdir(source_path):
31+
shutil.copytree(source_path, target_path, dirs_exist_ok=True)
32+
else:
33+
if os.path.exists(target_path):
34+
os.remove(target_path)
35+
shutil.copy2(source_path, target_path)
36+
37+
def delete_folders(folder_paths):
38+
"""
39+
Cleans existing folders for app and branches before executing the builds
40+
:param folder_paths: List of folders to be deleted under the current working directory
41+
"""
42+
for folder_path in folder_paths:
43+
try:
44+
shutil.rmtree(folder_path)
45+
print(f"Folder {folder_path} deletion successful.")
46+
except OSError as e:
47+
print(f"Error deleting folder: {e}")
48+
49+
def clone_data_to_branch_folder(branch_name, remote_url, parent_dir, pr_number=None):
50+
"""
51+
Clones data to branch folder in branch/<PR Number> or branch/dev folder
52+
:param branch_name: Branch to clone and build
53+
:param remote_url: Remote url for the git repository
54+
:param parent_dir: Parent directory to get context of where data is stored
55+
:param pr_number: PR number for the branch to host data into the folder
56+
"""
57+
common_dir = "branch"
58+
target_path = os.path.join(common_dir, pr_number)
59+
os.makedirs(target_path, exist_ok=True)
60+
os.chdir(target_path)
61+
subprocess.run(["git", "init"])
62+
subprocess.run(["git", "remote", "add", "origin", remote_url])
63+
print(f"Checking out branch {branch_name}")
64+
subprocess.run(["git", "fetch", "--depth", "1", "origin", branch_name])
65+
subprocess.run([
66+
"git", "checkout", "-b", branch_name, "--track",
67+
f"origin/{branch_name}"
68+
])
69+
install_mkdocs_with_pipenv()
70+
source_dir = os.path.join(os.getcwd(), "site")
71+
copy_folder(source_dir, os.path.join(parent_dir, "app", pr_number))
72+
os.chdir(parent_dir)
73+
74+
75+
def process_branch_folders():
76+
"""
77+
Clones the branch specific code to hosted/<branch-name> folder.
78+
It then executes the build command and copy the built site to apps folder
79+
under the same branch name
80+
:return: PR numbers in str list where the site data is copied to
81+
"""
82+
delete_folders(["branch", "app"])
83+
84+
command = ["gh", "pr", "list", "--json", "number,headRefName"]
85+
command_run_result = subprocess.run(command, capture_output=True, text=True).stdout.strip()
86+
branches_data = ast.literal_eval(command_run_result)
87+
remote_url = subprocess.run(["git", "remote", "get-url", "origin"],
88+
capture_output=True,
89+
text=True).stdout.strip()
90+
parent_dir = os.getcwd()
91+
clone_data_to_branch_folder("dev", remote_url, parent_dir, "dev")
92+
pr_numbers = []
93+
for branch_data in branches_data:
94+
if not branch_data["headRefName"].startswith("hosted/"):
95+
continue
96+
pr_number = str(branch_data["number"])
97+
clone_data_to_branch_folder(branch_data["headRefName"], remote_url, parent_dir, pr_number)
98+
pr_numbers.append(pr_number)
99+
100+
return pr_numbers
101+
102+
def update_nginx_config(pr_numbers):
103+
"""
104+
Updates nginx.conf file with branches built information to host multiple versions
105+
of software at the same time.
106+
:param pr_numbers: pr numbers a str list of open pr numbers to be hosted
107+
"""
108+
config_file = os.path.join(os.getcwd(), "nginx.conf")
109+
nginx_location_blocks = ""
110+
111+
for pr_number in pr_numbers:
112+
location_block = f"""location /{pr_number} {{
113+
alias /app/{pr_number};
114+
try_files $uri $uri/ /index.html;
115+
error_page 404 /404.html;
116+
}}
117+
"""
118+
nginx_location_blocks += location_block
119+
120+
with open(config_file, "r+") as f:
121+
content = f.read()
122+
content = content.replace("#REPLACE_APPS", nginx_location_blocks)
123+
f.seek(0)
124+
f.write(content)
125+
f.truncate()
126+
127+
print("NGINX configuration updated successfully!")
128+
129+
if __name__ == "__main__":
130+
current_dir = os.getcwd()
131+
pr_numbers = process_branch_folders()
132+
update_nginx_config(pr_numbers)

nginx.conf

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
worker_processes auto;
2+
3+
events {
4+
worker_connections 1024;
5+
}
6+
7+
http {
8+
include mime.types;
9+
default_type application/octet-stream;
10+
11+
sendfile on;
12+
keepalive_timeout 65;
13+
14+
server {
15+
listen 80;
16+
17+
#REPLACE_APPS
18+
location / {
19+
alias /app/dev/;
20+
index index.html;
21+
try_files $uri $uri/ /index.html;
22+
error_page 404 /404.html;
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)