Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .cdkr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
build:
- aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws
apps:
- basedir: infrastructure
app: python3 app.py
stacks:
- '*'
context:
gaggle-cdk:account: {{account_id}}
gaggle-cdk:environment: {{environment}}
gaggle-cdk:deploy-it-role: 'true'
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.7
python-version: 3.13
- name: install
run: |
python -m pip install --upgrade pip
Expand Down
4 changes: 2 additions & 2 deletions ecs_chargeback/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
boto3==1.26.52
tabulate==0.9.0
boto3
tabulate
datadog
102 changes: 18 additions & 84 deletions infrastructure/app.py
Original file line number Diff line number Diff line change
@@ -1,93 +1,22 @@
#!/usr/bin/env python3
import os
from os.path import join, dirname, abspath
import aws_cdk as cdk
from constructs import Construct
from aws_cdk import (
aws_lambda_python_alpha as aws_lambda_python,
aws_lambda,
aws_events,
aws_events_targets,
aws_s3,
aws_iam,
)
from gaggle_cdk.core import apply_permissions_boundary

base_path = dirname(dirname(abspath(__file__)))


class ChargebackStack(cdk.Stack):
def __init__(
self,
scope: Construct,
id: str,
cluster_tag: str,
run_frequency_mins: int,
cost_lookback_days: int,
datadog_metric_prefix: str,
dd_api_key_secret_id: str,
dd_api_key_secret_field: str = "api_key",
bucket_name: str = None,
**kwargs
) -> None:
super().__init__(scope, id, **kwargs)

cache_bucket = aws_s3.Bucket(
self,
"ChargebackCacheBucket",
bucket_name=bucket_name,
)

datadog_api_key = cdk.SecretValue.secrets_manager(
secret_id=dd_api_key_secret_id,
json_field=dd_api_key_secret_field,
)
from aws_cdk import App, Environment
from os.path import join, dirname, abspath
from gaggle_cdk.core import apply_permissions_boundary, GaggleTags

chargeback = aws_lambda_python.PythonFunction(
self,
"ChargebackHandler",
entry=join(base_path, "ecs_chargeback"),
runtime=aws_lambda.Runtime.PYTHON_3_8,
index="lambda.py",
handler="handler",
environment={
"CLUSTER_TAG": cluster_tag,
"CACHE_BUCKET": cache_bucket.bucket_name,
"UTILIZATION_LOOKBACK_MINS": str(run_frequency_mins),
"COST_LOOKBACK_DAYS": str(cost_lookback_days),
"DATADOG_API_KEY": datadog_api_key.to_string(),
"DATADOG_METRIC_PREFIX": datadog_metric_prefix,
},
timeout=cdk.Duration.seconds(60),
)
chargeback.add_to_role_policy(
aws_iam.PolicyStatement(
actions=[
"ecs:ListClusters",
"ecs:ListServices",
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"ecs:ListTagsForResource",
"cloudwatch:GetMetricData",
"ec2:DescribeInstanceTypes",
"ce:GetCostAndUsage",
],
resources=["*"],
)
)
from app_stack import ChargebackStack

cache_bucket.grant_read_write(chargeback.role)
app = App()

rule = aws_events.Rule(
self,
"ChargebackHandlerRule",
schedule=aws_events.Schedule.rate(cdk.Duration.minutes(run_frequency_mins)),
)
rule.add_target(aws_events_targets.LambdaFunction(chargeback))
base_path = dirname(dirname(abspath(__file__)))
app_name = "ecs-chargeback"
environment = "production"
account_id = app.node


app = cdk.App()
ChargebackStack(
## Production
app_stack = ChargebackStack(
app,
"ecs-chargeback",
cluster_tag=app.node.try_get_context("chargeback:cluster-tag"),
Expand All @@ -101,12 +30,17 @@ def __init__(
"chargeback:datadog-api-key-secret-field"
),
datadog_metric_prefix=app.node.try_get_context("chargeback:datadog-metric-prefix"),
env=cdk.Environment(
env=Environment(
account=os.environ["CDK_DEFAULT_ACCOUNT"],
region=os.environ["CDK_DEFAULT_REGION"],
),
)
GaggleTags(
application=app_name,
environment=environment,
team=GaggleTags.Team.DEVOPS,
).apply(app_stack)

apply_permissions_boundary(app)
apply_permissions_boundary(app_stack)

app.synth()
121 changes: 121 additions & 0 deletions infrastructure/app_stack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
import os

from aws_cdk import (
aws_lambda,
aws_events,
aws_events_targets,
aws_s3,
aws_iam,
BundlingOptions,
Duration,
SecretValue,
Stack,
)
from os.path import join, dirname, abspath
from constructs import Construct
from gaggle_cdk.core import apply_permissions_boundary
from gaggle_cdk.core.tagging import GaggleTags
from gaggle_cdk.core.teams import GaggleTeam

base_path = dirname(dirname(abspath(__file__)))

app_name = "ecs-chargeback"


class ChargebackStack(Stack):
def __init__(
self,
scope: Construct,
id: str,
*,
cluster_tag: str,
run_frequency_mins: int,
cost_lookback_days: int,
datadog_metric_prefix: str,
dd_api_key_secret_id: str,
dd_api_key_secret_field: str = "api_key",
bucket_name: str = None,
**kwargs
) -> None:
super().__init__(scope, id, **kwargs)

cache_bucket = aws_s3.Bucket(
self,
"ChargebackCacheBucket",
bucket_name=bucket_name,
)

datadog_api_key = SecretValue.secrets_manager(
secret_id=dd_api_key_secret_id,
json_field=dd_api_key_secret_field,
)

iam_role = aws_iam.Role(
self,
"ChargebackLambdaRole",
assumed_by=aws_iam.ServicePrincipal("lambda.amazonaws.com"),
managed_policies=[
aws_iam.ManagedPolicy.from_aws_managed_policy_name(
"service-role/AWSLambdaBasicExecutionRole"
)
],
inline_policies={
"LambdaPolicy": aws_iam.PolicyDocument(
statements=[
aws_iam.PolicyStatement(
effect=aws_iam.Effect.ALLOW,
actions=[
"ecs:ListClusters",
"ecs:ListServices",
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"ecs:ListTagsForResource",
"cloudwatch:GetMetricData",
"ec2:DescribeInstanceTypes",
"ce:GetCostAndUsage",
],
resources=["*"],
)
]
)
},
)

chargeback = aws_lambda.Function(
self,
"ChargebackHandler",
runtime=aws_lambda.Runtime.PYTHON_3_13,
function_name=app_name,
code=aws_lambda.Code.from_asset(
join(base_path, "ecs_chargeback"),
bundling=BundlingOptions(
image=aws_lambda.Runtime.PYTHON_3_13.bundling_image,
command=[
"bash",
"-c",
"pip install --no-cache -r requirements.txt -t /asset-output && cp -au . /asset-output",
],
),
),
handler="lambda.handler",
environment={
"CLUSTER_TAG": cluster_tag,
"CACHE_BUCKET": cache_bucket.bucket_name,
"UTILIZATION_LOOKBACK_MINS": str(run_frequency_mins),
"COST_LOOKBACK_DAYS": str(cost_lookback_days),
"DATADOG_API_KEY": datadog_api_key.to_string(),
"DATADOG_METRIC_PREFIX": datadog_metric_prefix,
},
timeout=Duration.seconds(60),
role=iam_role,
)

cache_bucket.grant_read_write(chargeback.role)

rule = aws_events.Rule(
self,
"ChargebackHandlerRule",
schedule=aws_events.Schedule.rate(Duration.minutes(run_frequency_mins)),
)
rule.add_target(aws_events_targets.LambdaFunction(chargeback))
7 changes: 3 additions & 4 deletions infrastructure/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
aws-cdk-lib==2.61.0
aws_cdk.aws_lambda_python_alpha==2.61.0a0
constructs>=10.0.0
gaggle-cdk.core==2.1.164
aws-cdk-lib
constructs
gaggle-cdk.core