Skip to content

Commit 05dd721

Browse files
ran-isenbergRan Isenberg
andauthored
feature: add staging and production (#752)
--------- Co-authored-by: Ran Isenberg <ran.isenberg@ranthebuilder.cloud>
1 parent a4c53b4 commit 05dd721

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+437
-255
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# This workflow will install Python dependencies, run tests and lint with a single version of Python
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3+
name: Main Branch - Serverless Service CI/CD
4+
5+
permissions:
6+
contents: read
7+
8+
env:
9+
NODE_VERSION: "18"
10+
PYTHON_VERSION: "3.11"
11+
AWS_REGION: "us-east-1"
12+
13+
on:
14+
workflow_dispatch:
15+
16+
push:
17+
branches: [main]
18+
19+
jobs:
20+
staging:
21+
runs-on: ubuntu-latest
22+
environment: staging
23+
permissions:
24+
id-token: write # required for requesting the JWT (GitHub OIDC)
25+
steps:
26+
- run: |
27+
echo "🎉 The job was automatically triggered by a ${{ env.EVENT_NAME }} event." >> $GITHUB_STEP_SUMMARY
28+
echo "🐧 This job is now running on a ${{ env.OS_NAME }} ${{env.OS_ARCH}} server hosted by GitHub!" >> $GITHUB_STEP_SUMMARY
29+
echo "🔎 The name of your branch is ${{ env.BRANCH_NAME }} and your repository is ${{ env.REPO_NAME }}." >> $GITHUB_STEP_SUMMARY
30+
env:
31+
EVENT_NAME: ${{ github.event_name}}
32+
OS_NAME: ${{ runner.os }}
33+
OS_ARCH: ${{runner.arch }}
34+
BRANCH_NAME: ${{ github.ref }}
35+
REPO_NAME: ${{ github.repository }}
36+
- name: Check out repository code
37+
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
38+
- name: Install poetry
39+
run: pipx install poetry
40+
- name: Set up Python
41+
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
42+
with:
43+
python-version: ${{ env.PYTHON_VERSION }}
44+
cache: "poetry" # NOTE: poetry must be installed before this step, or else cache doesn't work
45+
- name: Set up Node
46+
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
47+
with:
48+
node-version: ${{ env.NODE_VERSION }}
49+
cache: "npm"
50+
- name: Install dependencies
51+
run: make dev
52+
# NOTE: unit tests are connecting to AWS to instantiate boto3 clients/resources
53+
# once that's discussed we can move unit and infra tests as part of the fast quality standards
54+
# see https://github.com/ran-isenberg/serverless-python-demo/pull/38#discussion_r1299372169
55+
- name: Configure AWS credentials
56+
uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1
57+
with:
58+
role-to-assume: ${{ secrets['AWS_ROLE'] }}
59+
role-session-name: ${{ env.SESSION_NAME }}
60+
aws-region: ${{ env.AWS_REGION }}
61+
env:
62+
SESSION_NAME: "github-${{github.sha}}-staging"
63+
- name: Deploy to AWS
64+
run: make deploy
65+
env:
66+
ENVIRONMENT: staging # Custom environment variable
67+
# NOTE: these run unit and integration tests
68+
# we can look into coverage collection only later to make it faster and less brittle (--collect-only)
69+
- name: Code coverage tests
70+
run: make coverage-tests
71+
env:
72+
ENVIRONMENT: staging # Custom environment variable
73+
- name: Codecov
74+
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4
75+
with:
76+
files: ./coverage.xml
77+
fail_ci_if_error: false # optional (default = false)
78+
verbose: false # optional (default = false)
79+
- name: Run E2E tests
80+
run: make e2e
81+
env:
82+
ENVIRONMENT: staging # Custom environment variable
83+
84+
production:
85+
runs-on: ubuntu-latest
86+
needs: [staging]
87+
environment: production
88+
permissions:
89+
id-token: write # required for requesting the JWT (GitHub OIDC)
90+
steps:
91+
- run: |
92+
echo "🎉 The job was automatically triggered by a ${{ env.EVENT_NAME }} event." >> $GITHUB_STEP_SUMMARY
93+
echo "🐧 This job is now running on a ${{ env.OS_NAME }} ${{env.OS_ARCH}} server hosted by GitHub!" >> $GITHUB_STEP_SUMMARY
94+
echo "🔎 The name of your branch is ${{ env.BRANCH_NAME }} and your repository is ${{ env.REPO_NAME }}." >> $GITHUB_STEP_SUMMARY
95+
env:
96+
EVENT_NAME: ${{ github.event_name}}
97+
OS_NAME: ${{ runner.os }}
98+
OS_ARCH: ${{runner.arch }}
99+
BRANCH_NAME: ${{ github.ref }}
100+
REPO_NAME: ${{ github.repository }}
101+
- name: Check out repository code
102+
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
103+
- name: Install poetry
104+
run: pipx install poetry
105+
- name: Set up Python
106+
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
107+
with:
108+
python-version: ${{ env.PYTHON_VERSION }}
109+
cache: "poetry" # NOTE: poetry must be installed before this step, or else cache doesn't work
110+
- name: Set up Node
111+
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
112+
with:
113+
node-version: ${{ env.NODE_VERSION }}
114+
cache: "npm"
115+
- name: Install dependencies
116+
run: make dev
117+
# NOTE: unit tests are connecting to AWS to instantiate boto3 clients/resources
118+
# once that's discussed we can move unit and infra tests as part of the fast quality standards
119+
# see https://github.com/ran-isenberg/serverless-python-demo/pull/38#discussion_r1299372169
120+
- name: Configure AWS credentials
121+
uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1
122+
with:
123+
role-to-assume: ${{ secrets['AWS_ROLE'] }}
124+
role-session-name: ${{ env.SESSION_NAME }}
125+
aws-region: ${{ env.AWS_REGION }}
126+
env:
127+
SESSION_NAME: "github-${{github.sha}}-production"
128+
- name: Deploy to AWS
129+
run: make deploy
130+
env:
131+
ENVIRONMENT: production # Custom environment variable
132+
133+
publish_github_pages:
134+
runs-on: ubuntu-latest
135+
needs: [production]
136+
permissions:
137+
contents: write # for docs push
138+
if: contains('refs/heads/main', github.ref)
139+
steps:
140+
- name: Check out repository code
141+
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
142+
- name: Set up Python
143+
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
144+
with:
145+
python-version: ${{ env.PYTHON_VERSION }}
146+
- name: Set up Node
147+
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
148+
with:
149+
node-version: ${{ env.NODE_VERSION }}
150+
cache: "npm"
151+
- name: Install dependencies
152+
run: make dev
153+
- name: Generate docs
154+
run: |
155+
poetry run mkdocs gh-deploy --force

.github/workflows/serverless-service.yml renamed to .github/workflows/pr-serverless-service.yml

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This workflow will install Python dependencies, run tests and lint with a single version of Python
22
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3-
name: Serverless Service CI/CD
3+
name: PR - Serverless Service CI/CD
44

55
permissions:
66
contents: read
@@ -13,9 +13,6 @@ env:
1313
on:
1414
workflow_dispatch:
1515

16-
push:
17-
branches: [main]
18-
1916
pull_request:
2017
branches: [main]
2118

@@ -83,7 +80,7 @@ jobs:
8380
role-session-name: ${{ env.SESSION_NAME }}
8481
aws-region: ${{ env.AWS_REGION }}
8582
env:
86-
SESSION_NAME: "github-${{github.sha}}"
83+
SESSION_NAME: "github-${{github.sha}}-dev"
8784
- name: Unit tests
8885
run: make unit
8986
- name: Infrastructure tests
@@ -105,27 +102,3 @@ jobs:
105102
- name: Destroy stack
106103
if: always()
107104
run: make destroy
108-
109-
publish_github_pages:
110-
runs-on: ubuntu-latest
111-
needs: [tests]
112-
permissions:
113-
contents: write # for docs push
114-
if: contains('refs/heads/main', github.ref)
115-
steps:
116-
- name: Check out repository code
117-
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
118-
- name: Set up Python
119-
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
120-
with:
121-
python-version: ${{ env.PYTHON_VERSION }}
122-
- name: Set up Node
123-
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
124-
with:
125-
node-version: ${{ env.NODE_VERSION }}
126-
cache: "npm"
127-
- name: Install dependencies
128-
run: make dev
129-
- name: Generate docs
130-
run: |
131-
poetry run mkdocs gh-deploy --force

app.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44
from aws_cdk import App, Environment
55
from boto3 import client, session
66

7-
from cdk.service.service_stack import ServiceStack, get_stack_name
7+
from cdk.service.service_stack import ServiceStack
8+
from cdk.service.utils import get_stack_name
89

910
account = client('sts').get_caller_identity()['Account']
1011
region = session.Session().region_name
12+
environment = os.getenv('ENVIRONMENT', 'dev')
1113
app = App()
1214
my_stack = ServiceStack(
13-
app,
14-
get_stack_name(),
15+
scope=app,
16+
id=get_stack_name(),
1517
env=Environment(account=os.environ.get('AWS_DEFAULT_ACCOUNT', account), region=os.environ.get('AWS_DEFAULT_REGION', region)),
18+
is_production_env=True if environment == 'production' else False,
1619
)
1720

1821
app.synth()

cdk/service/api_db_construct.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def _build_db(self, id_prefix: str) -> dynamodb.Table:
3535
self,
3636
table_id,
3737
table_name=table_id,
38-
partition_key=dynamodb.Attribute(name='order_id', type=dynamodb.AttributeType.STRING),
38+
partition_key=dynamodb.Attribute(name='id', type=dynamodb.AttributeType.STRING),
3939
billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
4040
point_in_time_recovery=True,
4141
removal_policy=RemovalPolicy.DESTROY,

cdk/service/constants.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
LAMBDA_BASIC_EXECUTION_ROLE = 'AWSLambdaBasicExecutionRole'
33
SERVICE_ROLE = 'ServiceRole'
44
CREATE_LAMBDA = 'CreateOrder'
5-
TABLE_NAME = 'orders'
5+
TABLE_NAME = 'ordersDb'
66
IDEMPOTENCY_TABLE_NAME = 'IdempotencyTable'
77
TABLE_NAME_OUTPUT = 'DbOutput'
88
IDEMPOTENCY_TABLE_NAME_OUTPUT = 'IdempotencyDbOutput'
@@ -14,6 +14,8 @@
1414
API_HANDLER_LAMBDA_TIMEOUT = 10 # seconds
1515
POWERTOOLS_SERVICE_NAME = 'POWERTOOLS_SERVICE_NAME'
1616
SERVICE_NAME = 'Orders'
17+
SERVICE_NAME_TAG = 'service'
18+
OWNER_TAG = 'owner'
1719
METRICS_NAMESPACE = 'orders_kpi'
1820
METRICS_DIMENSION_KEY = 'service'
1921
POWERTOOLS_TRACE_DISABLED = 'POWERTOOLS_TRACE_DISABLED'

cdk/service/service_stack.py

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,43 @@
1-
import getpass
2-
from pathlib import Path
3-
41
from aws_cdk import Aspects, Stack, Tags
52
from cdk_nag import AwsSolutionsChecks, NagSuppressions
63
from constructs import Construct
7-
from git import Repo
84

95
from cdk.service.api_construct import ApiConstruct
106
from cdk.service.configuration.configuration_construct import ConfigurationStore
11-
from cdk.service.constants import CONFIGURATION_NAME, ENVIRONMENT, SERVICE_NAME
12-
13-
14-
def get_username() -> str:
15-
try:
16-
return getpass.getuser().replace('.', '-')
17-
except Exception:
18-
return 'github'
19-
20-
21-
def get_stack_name() -> str:
22-
repo = Repo(Path.cwd())
23-
username = get_username()
24-
try:
25-
branch_name = f'{repo.active_branch}'.replace('/', '-').replace('_', '-')
26-
return f'{username}-{branch_name}-{SERVICE_NAME}'
27-
except TypeError:
28-
# we're running in detached mode (HEAD)
29-
# see https://github.com/gitpython-developers/GitPython/issues/633
30-
return f'{username}-{SERVICE_NAME}'
7+
from cdk.service.constants import CONFIGURATION_NAME, ENVIRONMENT, OWNER_TAG, SERVICE_NAME, SERVICE_NAME_TAG
8+
from cdk.service.utils import get_construct_name, get_username
319

3210

3311
class ServiceStack(Stack):
3412

35-
def __init__(self, scope: Construct, id: str, **kwargs) -> None:
13+
def __init__(self, scope: Construct, id: str, is_production_env: bool, **kwargs) -> None:
3614
super().__init__(scope, id, **kwargs)
37-
Tags.of(self).add('service_name', 'Order')
15+
self._add_stack_tags()
3816

3917
# This construct should be deployed in a different repo and have its own pipeline so updates can be decoupled
4018
# from running the service pipeline and without redeploying the service lambdas. For the sake of this template
4119
# example, it is deployed as part of the service stack
42-
self.dynamic_configuration = ConfigurationStore(self, f'{id}dynamic_conf'[0:64], ENVIRONMENT, SERVICE_NAME, CONFIGURATION_NAME)
43-
self.api = ApiConstruct(self, f'{id}Service'[0:64], self.dynamic_configuration.app_name)
20+
self.dynamic_configuration = ConfigurationStore(
21+
self,
22+
get_construct_name(stack_prefix=id, construct_name='DynamicConf'),
23+
ENVIRONMENT,
24+
SERVICE_NAME,
25+
CONFIGURATION_NAME,
26+
)
27+
self.api = ApiConstruct(
28+
self,
29+
get_construct_name(stack_prefix=id, construct_name='Crud'),
30+
self.dynamic_configuration.app_name,
31+
)
4432

4533
# add security check
4634
self._add_security_tests()
4735

36+
def _add_stack_tags(self) -> None:
37+
# best practice to help identify resources in the console
38+
Tags.of(self).add(SERVICE_NAME_TAG, SERVICE_NAME)
39+
Tags.of(self).add(OWNER_TAG, get_username())
40+
4841
def _add_security_tests(self) -> None:
4942
Aspects.of(self).add(AwsSolutionsChecks(verbose=True))
5043
# Suppress a specific rule for this resource

cdk/service/utils.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import getpass
2+
import os
3+
from pathlib import Path
4+
5+
from git import Repo
6+
7+
import cdk.service.constants as constants
8+
9+
10+
def get_username() -> str:
11+
try:
12+
return getpass.getuser().replace('.', '-')
13+
except Exception:
14+
return 'github'
15+
16+
17+
def get_stack_name() -> str:
18+
repo = Repo(Path.cwd())
19+
username = get_username()
20+
cicd_environment = os.getenv('ENVIRONMENT', 'dev')
21+
try:
22+
branch_name = f'{repo.active_branch}'.replace('/', '-').replace('_', '-')
23+
return f'{username}-{branch_name}-{constants.SERVICE_NAME}-{cicd_environment}'
24+
except TypeError:
25+
# we're running in detached mode (HEAD)
26+
# see https://github.com/gitpython-developers/GitPython/issues/633
27+
return f'{username}-{constants.SERVICE_NAME}-{cicd_environment}'
28+
29+
30+
def get_construct_name(stack_prefix: str, construct_name: str) -> str:
31+
return f'{stack_prefix}-{construct_name}'[0:64]

docs/examples/best_practices/dynamic_configuration/evaluate_feature_flags.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
from aws_lambda_env_modeler import init_environment_variables
66
from aws_lambda_powertools.utilities.typing import LambdaContext
77

8-
from service.handlers.schemas.dynamic_configuration import MyConfiguration
9-
from service.handlers.schemas.env_vars import MyHandlerEnvVars
8+
from service.handlers.models.dynamic_configuration import MyConfiguration
9+
from service.handlers.models.env_vars import MyHandlerEnvVars
1010
from service.handlers.utils.dynamic_configuration import get_configuration_store, parse_configuration
1111
from service.handlers.utils.observability import logger
1212

docs/examples/best_practices/dynamic_configuration/parse_configuration.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
from aws_lambda_env_modeler import init_environment_variables
66
from aws_lambda_powertools.utilities.typing import LambdaContext
77

8-
from service.handlers.schemas.dynamic_configuration import MyConfiguration
9-
from service.handlers.schemas.env_vars import MyHandlerEnvVars
8+
from service.handlers.models.dynamic_configuration import MyConfiguration
9+
from service.handlers.models.env_vars import MyHandlerEnvVars
1010
from service.handlers.utils.dynamic_configuration import parse_configuration
1111
from service.handlers.utils.observability import logger
1212

docs/examples/best_practices/environment_variables/getter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from aws_lambda_env_modeler import get_environment_variables, init_environment_variables
66
from aws_lambda_powertools.utilities.typing import LambdaContext
77

8-
from service.handlers.schemas.env_vars import MyHandlerEnvVars
8+
from service.handlers.models.env_vars import MyHandlerEnvVars
99

1010

1111
@init_environment_variables(model=MyHandlerEnvVars)

0 commit comments

Comments
 (0)