-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added github workflow, made the staging environment deployable (#137)
- Loading branch information
Showing
14 changed files
with
645 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
boto3 | ||
boto3-stubs[ecr] | ||
botocore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }} |
Oops, something went wrong.