Skip to content

Commit

Permalink
Added github workflow, made the staging environment deployable (#137)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickkats committed Feb 23, 2024
1 parent 1063c09 commit 465448d
Show file tree
Hide file tree
Showing 14 changed files with 645 additions and 16 deletions.
58 changes: 58 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
### :scroll: Description

- Describe the general shape of this PR (new feature? refactor? bug fix? one-line change?)
<!--- Add your answer here -->

- Describe what changes are being made
<!--- Add your answer here -->

- Describe why these changes are being made
<!--- Add your answer here -->

- List the use cases and edge cases relevant to this PR
<!--- Add your answer here -->

- Describe how errors will be handled. How will we know if this code breaks in production
<!--- Add your answer here -->

- Describe any external libraries/dependencies added or removed in this PR
<!--- Add your answer here -->

- Describe any security risks are there regarding this change
<!--- Add your answer here -->

- Describe how you tested these changes
<!--- Add your answer here -->

- Link to relevant external documentation
<!--- Add your answer here -->
<!--- Example: API docs, architecture diagrams, MDN docs -->


--------------------------
### :clipboard: Mandatory Checklist

- [x] Example of a checked item (please remove when creating your Pull Request)

- [ ] Linked to the Github Issues being addressed using the right sidebar :arrow_right:
- [ ] Have you discussed these changes with the project leader(s)?
- [ ] Do all variable and function names communicate what they do?
- [ ] Were all the changes commented and / or documented?
- [ ] Is the PR the right size? (If the PR is too large to review, it might be good to break it up into multiple PRs.)
- [ ] Does all work in progress, temporary, or debugger code have a TODO comment with links to Github issues?
- [ ] *If you changed the user interface, did you add before and after screenshots to below?*

--------------------------
### :framed_picture: Screenshots and Screen Recordings

#### Before
<!--- Add before screenshots here -->


#### After
<!--- Add after screenshots here -->


--------------------------
### :blue_book: Glossary
- PR = Pull Request
132 changes: 132 additions & 0 deletions .github/scripts/argocd_deploy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import logging
import typing as t
from argparse import ArgumentParser, Namespace
from json import loads
from logging import Formatter, StreamHandler
from math import floor
from os import environ, getenv
from subprocess import run as subproccess_run
from sys import exit, stdout


# Wrapper for ArgoCD CLI
# https://argo-cd.readthedocs.io/en/stable/user-guide/commands/argocd/
class ArgoCDeployer:
def __init__(self, application: str, docker_tag: str) -> None:
self.application = application or getenv('ARGO_CD_APPLICATION', environ['DEPLOYMENT'])
self.docker_tag = docker_tag or environ['DOCKER_TAG']
self.image_tag_parameter = getenv('IMAGE_TAG_PARAMETER', 'global.image.tag=')
self.argo_cd_credentials = [
f'--username={environ["ARGO_CD_USERNAME"]}',
f'--password={environ["ARGO_CD_PASSWORD"]}',
]

def _execute_cli(self, command: t.List[str], return_stdout: bool = False) -> t.Optional[str]:
command = ['argocd'] + command
LOG.info(' '.join(command).replace(environ['ARGO_CD_PASSWORD'], 'redacted'))
output = subproccess_run(command, capture_output=True, encoding='utf-8')
if output.returncode != 0:
LOG.fatal(f'ArgoCD CLI failed {command=} returncode={output.returncode} standard_error={output.stderr}')
if return_stdout:
return output.stdout

def login(self):
self._execute_cli(
command=[
'login',
'--grpc-web',
environ['ARGO_CD_SERVER'],
*self.argo_cd_credentials,
]
)

def update_image_tag(self):
args = []
parameters = [
f'{self.image_tag_parameter}{self.docker_tag}',
]
for parameter in parameters:
args.extend(['--parameter', parameter])
self._execute_cli(command=['app', 'set', self.application, *args])

def sync_app(self, preview: bool = False, timeout: int = 1200):
args = [
'--assumeYes',
'--prune',
'--retry-backoff-max-duration',
f'{floor(timeout/60)}m',
'--timeout',
str(timeout + 30),
]
if preview:
args.append('--dry-run')
self._execute_cli(command=['app', 'sync', self.application, *args])

def preview_deploy(self):
self.sync_app(preview=True)

def wait_health(self):
args = [
'--health',
'--sync',
'--timeout',
'600',
]
self._execute_cli(command=['app', 'wait', self.application, *args])

def verify_health(self):
args = [
'--output',
'json',
]
info = self._execute_cli(command=['app', 'get', self.application, *args], return_stdout=True)
info = loads(info)
status = info['status']
if status['health']['status'] == 'Degraded':
LOG.critical(
f'Application is unhealthy, aborting deploy\nCheck Application https://ARGO_CD_SERVER/applications/argocd/{self.application}?view=network&resource='
)
exit(1)
sync_status = status['sync']['status']
if sync_status not in ['Synced', 'OutofSync']:
LOG.info('Application non-deployable SyncStatus={sync_status} - Will try to wait before aborting')
self.wait_health()


def Logger(logger_name):
logger = logging.getLogger(logger_name)

logger.setLevel(logging.INFO)

standard_output = StreamHandler(stdout)
standard_output.setFormatter(
Formatter(
'{asctime} {name} {levelname:8} {message}',
'%Y-%m-%d %H:%M:%S',
'{',
)
)
logger.addHandler(standard_output)
return logger


if __name__ == '__main__':
LOG = Logger('argocd.deploy')

parser = ArgumentParser(
prog='ArgoCDeployer',
description='Deploy ArgoCD Applications utilizing argocd CLI',
)
# TODO: Convert arguments to non-positional args and enforce required=True
# Requires CICD changes where command is utilized
parser.add_argument('--application', help='ArgoCD Application Name i.e., foobar-dev')
parser.add_argument('--docker_tag', help='Docker Tag to be deployed, usually the full Git SHA')
args: Namespace = parser.parse_args()

argocd = ArgoCDeployer(application=args.application, docker_tag=args.docker_tag)
argocd.login()
argocd.verify_health()
argocd.update_image_tag()
argocd.sync_app()

LOG.info(f'Deploy succeeded {args.application} docker_tag={args.docker_tag}')
3 changes: 3 additions & 0 deletions .github/scripts/requirements.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
boto3
boto3-stubs[ecr]
botocore
39 changes: 39 additions & 0 deletions .github/scripts/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile requirements.in
#
boto3==1.34.7
# via -r requirements.in
boto3-stubs[ecr]==1.34.7
# via -r requirements.in
botocore==1.34.7
# via
# -r requirements.in
# boto3
# s3transfer
botocore-stubs==1.34.7
# via boto3-stubs
jmespath==1.0.1
# via
# boto3
# botocore
mypy-boto3-ecr==1.34.0
# via boto3-stubs
python-dateutil==2.8.2
# via botocore
s3transfer==0.10.0
# via boto3
six==1.16.0
# via python-dateutil
types-awscrt==0.20.0
# via botocore-stubs
types-s3transfer==0.10.0
# via boto3-stubs
typing-extensions==4.9.0
# via
# boto3-stubs
# mypy-boto3-ecr
urllib3==2.0.7
# via botocore
92 changes: 92 additions & 0 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
name: deploy
run-name: ${{ github.event.inputs.env }} deploy ${{ github.ref_name }}
on:
workflow_dispatch:
inputs:
env:
type: environment
description: 'ArgoCD Deployed environment'
ref:
description: 'Branch, Tag, or Full SHA'
required: true
default: 'master'

concurrency:
group: ${{ github.event.inputs.env }}

env:
REFERENCE: ${{ github.event.pull_request.head.sha || github.event.push.head_commit.id }}

jobs:
build:
environment:
name: ${{ github.event.inputs.env }}
url: ${{ vars.PUBLIC_URL }}
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
strategy:
matrix:
image: [build, backend]
steps:
- uses: actions/checkout@v4
with:
ref: ${{ env.REFERENCE }}
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions
role-session-name: gha
aws-region: us-west-2
- uses: docker/setup-buildx-action@v3
- id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
with:
mask-password: 'true'
- run: make build-${{ matrix.image }}
env:
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
LOG_LEVEL: ${{ vars.SERVER_LOG_LEVEL }}
REACT_APP_API_SERVER: ${{ vars.REACT_APP_API_SERVER }}
PUBLIC_URL: ${{ vars.PUBLIC_URL }}
REACT_APP_API_BASE_URL: ${{ vars.API_BASE_URL }}
API_BASE_URL: ${{ vars.API_BASE_URL }}


deploy:
needs: build
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
environment:
name: ${{ github.event.inputs.env }}
url: ${{ vars.PUBLIC_URL }}
env:
ENVIRONMENT: ${{ github.event.inputs.env }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.ref }}
- uses: clowdhaus/argo-cd-action/@main
with:
version: 2.10.0
command: version
options: --client
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- run: pip install -r .github/scripts/requirements.txt
- name: deploy
run: |-
python .github/scripts/argocd_deploy.py \
--application=heartofthevalley-${{ env.ENVIRONMENT }} \
--docker_tag=$(git rev-parse HEAD)
env:
ARGO_CD_SERVER: ${{ secrets.ARGO_CD_SERVER }}
ARGO_CD_USERNAME: ${{ secrets.ARGO_CD_USERNAME }}
ARGO_CD_PASSWORD: ${{ secrets.ARGO_CD_PASSWORD }}
REACT_APP_API_BASE_URL: ${{ vars.API_BASE_URL }}
API_BASE_URL: ${{ vars.API_BASE_URL }}

0 comments on commit 465448d

Please sign in to comment.