Skip to content

Commit 162f865

Browse files
authored
print-events: add lambda that prints events (#297)
This is a useful example for a few reasons: 1. It demonstrates how to do assumable identities and use the Chainguard API with Python 2. It demonstrates how to consume events in Python 3. It demonstrates how to resolve details about an event from the API Customers could build on top of this by sending notifications to Slack or some other alerting system for events they care about.
1 parent e584310 commit 162f865

10 files changed

Lines changed: 558 additions & 0 deletions

File tree

print-events/Dockerfile

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
ARG FUNCTION_DIR="/function"
2+
3+
# Define Chainguard org to pull images from
4+
ARG CHAINGUARD_ORG=chainguard
5+
6+
# Use -dev variant to install dependencies with pip
7+
FROM cgr.dev/${CHAINGUARD_ORG}/python:latest-dev AS build-image
8+
9+
# Include global arg in this stage of the build
10+
ARG FUNCTION_DIR
11+
12+
# Create work dir
13+
WORKDIR ${FUNCTION_DIR}
14+
15+
# Install the function's dependencies
16+
COPY requirements.txt requirements.txt
17+
RUN pip install \
18+
--target ${FUNCTION_DIR} \
19+
-r requirements.txt && rm requirements.txt
20+
21+
# Copy function code
22+
COPY lambda_function.py lambda_function.py
23+
24+
# Use a prod version of the base Python image to reduce the final image size and
25+
# exclude unnecessary packages at runtime
26+
FROM cgr.dev/${CHAINGUARD_ORG}/python:latest
27+
28+
# Include global arg in this stage of the build
29+
ARG FUNCTION_DIR
30+
31+
# Set working directory to function root directory
32+
WORKDIR ${FUNCTION_DIR}
33+
34+
# Copy in the built dependencies
35+
COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR}
36+
37+
# Set runtime interface client as default command for the container runtime
38+
ENTRYPOINT [ "/usr/bin/python", "-m", "awslambdaric" ]
39+
40+
# Pass the name of the function handler as an argument to the runtime
41+
CMD [ "lambda_function.handler" ]

print-events/README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# print-events
2+
3+
This example deploys an AWS Lambda function that subscribes to events from a
4+
Chainguard organization and logs structured information about each event to
5+
CloudWatch.
6+
7+
```
8+
START RequestId: 4f655d07-0fb9-427d-8d87-f690c816cc5b Version: $LATEST
9+
TYPE: dev.chainguard.api.platform.registry.repo.created.v1
10+
IDENTITY_ID: af2e0885544401049c4b2bbea8d9aaff297a76b2
11+
EMAIL: user.name@your.org
12+
NAME: nginx
13+
BODY: {"actor": {"subject": "af2e08....}
14+
REPORT RequestId: 4f655d07-0fb9-427d-8d87-f690c816cc5b Duration: 1.38 ms Billed Duration: 2 ms Memory Size: 128 MB Max Memory Used: 83 MB
15+
END RequestId: 4f655d07-0fb9-427d-8d87-f690c816cc5b
16+
```
17+
18+
It is written in Python and demonstrates using the API to resolve useful details
19+
that are not present in the original event.
20+
21+
## Requirements
22+
23+
- [Terraform](https://developer.hashicorp.com/terraform/install)
24+
- [Docker](https://docs.docker.com/get-docker/) with the daemon running
25+
- The `aws` CLI
26+
- An AWS account with credentials configured (e.g. via `aws configure` or
27+
environment variables)
28+
- A Chainguard account with an organization. You can sign up at
29+
[console.chainguard.dev](https://console.chainguard.dev).
30+
- The `chainctl` CLI
31+
32+
33+
## Deployment
34+
35+
1. Authenticate to AWS and AWS ECR
36+
37+
```
38+
aws sso login
39+
aws ecr get-login-password --region <region> | \
40+
docker login --username AWS --password-stdin \
41+
<account-id>.dkr.ecr.<region>.amazonaws.com
42+
```
43+
44+
2. Authenticate to Chainguard
45+
46+
```
47+
chainctl auth login
48+
```
49+
50+
3. Create `iac/terraform.tfvars` and set your Chainguard organization name:
51+
52+
```hcl
53+
org_name = "your.org"
54+
55+
# Optional. Limit processing to specific CloudEvents types. If omitted, all
56+
# event types are processed.
57+
# ce_types = ["dev.chainguard.api.platform.registry.repo.created.v1"]
58+
```
59+
60+
2. Initialize Terraform:
61+
62+
```
63+
cd iac
64+
terraform init
65+
```
66+
67+
3. Apply:
68+
69+
```
70+
terraform apply
71+
```
72+
73+
Terraform will build and push the container image, then provision all AWS
74+
and Chainguard resources. The Lambda function URL is printed as an output:
75+
76+
```
77+
url = "https://<id>.lambda-url.<region>.on.aws/"
78+
```
79+
80+
Chainguard will begin delivering events to this URL immediately after the
81+
subscription is created.
82+
83+
## Viewing logs
84+
85+
Logs are written to CloudWatch under the log group
86+
`/aws/lambda/print-events`. You can tail them with the AWS CLI:
87+
88+
```
89+
aws logs tail /aws/lambda/print-events --follow
90+
```

print-events/iac/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.tfvars

print-events/iac/chainguard.tf

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
data "aws_caller_identity" "current" {}
2+
3+
data "chainguard_group" "group" {
4+
name = var.org_name
5+
}
6+
7+
data "aws_iam_outbound_web_identity_federation" "issuer" {}
8+
9+
resource "chainguard_identity" "aws" {
10+
parent_id = data.chainguard_group.group.id
11+
name = "aws-lambda-identity"
12+
description = "Identity for AWS Lambda"
13+
14+
claim_match {
15+
issuer = data.aws_iam_outbound_web_identity_federation.issuer.issuer_identifier
16+
subject = aws_iam_role.lambda.arn
17+
}
18+
}
19+
20+
# Create a subscription to notify the Lambda function on changes under the root group.
21+
resource "chainguard_subscription" "subscription" {
22+
parent_id = data.chainguard_group.group.id
23+
sink = aws_lambda_function_url.lambda.function_url
24+
}
25+
26+
data "chainguard_role" "viewer" {
27+
name = "viewer"
28+
}
29+
30+
resource "chainguard_rolebinding" "viewer" {
31+
identity = chainguard_identity.aws.id
32+
role = data.chainguard_role.viewer.items[0].id
33+
group = data.chainguard_group.group.id
34+
}

print-events/iac/lambda.tf

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
data "aws_iam_policy_document" "lambda" {
2+
statement {
3+
effect = "Allow"
4+
actions = ["sts:AssumeRole"]
5+
principals {
6+
type = "Service"
7+
identifiers = ["lambda.amazonaws.com"]
8+
}
9+
}
10+
}
11+
12+
data "aws_iam_policy" "lambda" {
13+
name = "AWSLambdaBasicExecutionRole"
14+
}
15+
16+
resource "aws_iam_role" "lambda" {
17+
name = "print-events"
18+
assume_role_policy = data.aws_iam_policy_document.lambda.json
19+
managed_policy_arns = [data.aws_iam_policy.lambda.arn]
20+
}
21+
22+
resource "aws_iam_role_policy" "web_identity_token_policy" {
23+
name = "print-events-web-identity-token-policy"
24+
role = aws_iam_role.lambda.id
25+
26+
policy = jsonencode({
27+
Version = "2012-10-17"
28+
Statement = [
29+
{
30+
Effect = "Allow"
31+
Action = "sts:GetWebIdentityToken"
32+
Resource = "*"
33+
Condition = {
34+
"ForAnyValue:StringEquals" = {
35+
"sts:IdentityTokenAudience" = "https://issuer.enforce.dev"
36+
}
37+
"NumericLessThanEquals" : {
38+
"sts:DurationSeconds" : 300
39+
}
40+
}
41+
}
42+
]
43+
})
44+
}
45+
46+
locals {
47+
source_hash = sha1(join("", [
48+
filesha1("${path.module}/../Dockerfile"),
49+
filesha1("${path.module}/../lambda_function.py"),
50+
filesha1("${path.module}/../requirements.txt"),
51+
]))
52+
}
53+
54+
resource "aws_ecr_repository" "lambda" {
55+
name = "print-events"
56+
force_delete = true
57+
}
58+
59+
provider "docker" {
60+
registry_auth {
61+
address = split("/", aws_ecr_repository.lambda.repository_url)[0]
62+
config_file = pathexpand("~/.docker/config.json")
63+
}
64+
}
65+
66+
resource "docker_image" "lambda" {
67+
name = aws_ecr_repository.lambda.repository_url
68+
build {
69+
context = abspath("${path.module}/..")
70+
dockerfile = abspath("${path.module}/../Dockerfile")
71+
platform = "linux/amd64"
72+
}
73+
triggers = {
74+
source_hash = local.source_hash
75+
}
76+
}
77+
78+
resource "docker_registry_image" "lambda" {
79+
name = docker_image.lambda.name
80+
keep_remotely = true
81+
triggers = {
82+
image_id = docker_image.lambda.image_id
83+
}
84+
}
85+
86+
resource "aws_lambda_function" "lambda" {
87+
function_name = "print-events"
88+
role = aws_iam_role.lambda.arn
89+
package_type = "Image"
90+
image_uri = "${docker_registry_image.lambda.name}@${docker_registry_image.lambda.sha256_digest}"
91+
timeout = 30
92+
93+
environment {
94+
variables = {
95+
ORG_ID = data.chainguard_group.group.id
96+
IDENTITY = chainguard_identity.aws.id
97+
CE_TYPES = join(",", var.ce_types)
98+
}
99+
}
100+
}
101+
102+
resource "aws_lambda_function_url" "lambda" {
103+
function_name = aws_lambda_function.lambda.function_name
104+
authorization_type = "NONE"
105+
}

print-events/iac/main.tf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
terraform {
2+
required_providers {
3+
aws = { source = "hashicorp/aws" }
4+
chainguard = { source = "chainguard-dev/chainguard" }
5+
docker = { source = "kreuzwerker/docker" }
6+
}
7+
}

print-events/iac/outputs.tf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
output "url" {
2+
value = aws_lambda_function_url.lambda.function_url
3+
}

print-events/iac/variables.tf

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
variable "org_name" {
2+
type = string
3+
description = "The name of your Chainguard organization. For instance: 'your.org.com'."
4+
}
5+
6+
variable "ce_types" {
7+
type = list(string)
8+
description = "Optional list of CloudEvents types to process. Events with a Ce-Type not in this list will be ignored. If empty, all event types are processed."
9+
default = []
10+
}

0 commit comments

Comments
 (0)