Skip to content

Commit b1afe1a

Browse files
committed
Add AWS Lambda deployment infrastructure with GitHub Actions OIDC
1 parent 0b2ec10 commit b1afe1a

File tree

10 files changed

+1091
-1
lines changed

10 files changed

+1091
-1
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
name: Deploy to AWS Lambda
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- develop
8+
pull_request:
9+
branches:
10+
- main
11+
workflow_dispatch:
12+
inputs:
13+
environment:
14+
description: 'Environment to deploy to'
15+
required: true
16+
default: 'dev'
17+
type: choice
18+
options:
19+
- dev
20+
- staging
21+
- prod
22+
23+
permissions:
24+
id-token: write
25+
contents: read
26+
27+
env:
28+
AWS_REGION: ap-northeast-2
29+
ECR_REPOSITORY: invidious-api-lambda
30+
31+
jobs:
32+
build-and-deploy:
33+
runs-on: ubuntu-latest
34+
35+
strategy:
36+
matrix:
37+
include:
38+
- environment: dev
39+
branch: develop
40+
- environment: prod
41+
branch: main
42+
43+
# Only run the matrix job if the branch matches
44+
if: |
45+
(github.event_name == 'workflow_dispatch') ||
46+
(github.event_name == 'push' && github.ref_name == matrix.branch) ||
47+
(github.event_name == 'pull_request')
48+
49+
environment: ${{ matrix.environment }}
50+
51+
steps:
52+
- name: Checkout code
53+
uses: actions/checkout@v4
54+
with:
55+
fetch-depth: 0 # Need full history for git versioning
56+
57+
- name: Configure AWS credentials
58+
uses: aws-actions/configure-aws-credentials@v4
59+
with:
60+
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
61+
role-session-name: github-actions-${{ github.run_id }}
62+
aws-region: ${{ env.AWS_REGION }}
63+
64+
- name: Login to Amazon ECR
65+
id: login-ecr
66+
uses: aws-actions/amazon-ecr-login@v2
67+
68+
- name: Set up Docker Buildx
69+
uses: docker/setup-buildx-action@v3
70+
71+
- name: Generate image metadata
72+
id: meta
73+
run: |
74+
# Generate version tag
75+
VERSION=$(git describe --tags --always --dirty)
76+
TIMESTAMP=$(date +%Y%m%d%H%M%S)
77+
78+
# Set image tags
79+
if [ "${{ matrix.environment }}" == "prod" ]; then
80+
TAGS="${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:latest"
81+
TAGS="$TAGS,${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:$VERSION"
82+
TAGS="$TAGS,${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:prod-$TIMESTAMP"
83+
else
84+
TAGS="${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ matrix.environment }}"
85+
TAGS="$TAGS,${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ matrix.environment }}-$VERSION"
86+
TAGS="$TAGS,${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ matrix.environment }}-$TIMESTAMP"
87+
fi
88+
89+
echo "tags=$TAGS" >> $GITHUB_OUTPUT
90+
echo "version=$VERSION" >> $GITHUB_OUTPUT
91+
echo "timestamp=$TIMESTAMP" >> $GITHUB_OUTPUT
92+
93+
- name: Build and push Docker image
94+
uses: docker/build-push-action@v5
95+
with:
96+
context: .
97+
file: docker/Dockerfile.lambda
98+
platforms: linux/amd64,linux/arm64
99+
push: true
100+
tags: ${{ steps.meta.outputs.tags }}
101+
cache-from: type=gha
102+
cache-to: type=gha,mode=max
103+
build-args: |
104+
BUILD_DATE=${{ steps.meta.outputs.timestamp }}
105+
VERSION=${{ steps.meta.outputs.version }}
106+
107+
- name: Setup Terraform
108+
uses: hashicorp/setup-terraform@v3
109+
with:
110+
terraform_version: "1.5.0"
111+
112+
- name: Terraform Init
113+
working-directory: terraform
114+
run: |
115+
terraform init \
116+
-backend-config="bucket=${{ secrets.TERRAFORM_STATE_BUCKET }}" \
117+
-backend-config="key=${{ matrix.environment }}/invidious-lambda/terraform.tfstate" \
118+
-backend-config="region=${{ env.AWS_REGION }}" \
119+
-backend-config="dynamodb_table=${{ secrets.TERRAFORM_STATE_LOCK_TABLE }}"
120+
121+
- name: Terraform Plan
122+
working-directory: terraform
123+
run: |
124+
terraform plan \
125+
-var="environment=${{ matrix.environment }}" \
126+
-var="image_tag=${{ matrix.environment }}-${{ steps.meta.outputs.version }}" \
127+
-var="aws_region=${{ env.AWS_REGION }}" \
128+
-out=tfplan
129+
130+
- name: Terraform Apply
131+
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
132+
working-directory: terraform
133+
run: |
134+
terraform apply -auto-approve tfplan
135+
136+
- name: Update Lambda function
137+
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
138+
run: |
139+
# Get the latest image URI
140+
IMAGE_URI="${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ matrix.environment }}-${{ steps.meta.outputs.version }}"
141+
142+
# Update Lambda function with new image
143+
aws lambda update-function-code \
144+
--function-name invidious-api-${{ matrix.environment }} \
145+
--image-uri $IMAGE_URI \
146+
--region ${{ env.AWS_REGION }}
147+
148+
# Wait for update to complete
149+
aws lambda wait function-updated \
150+
--function-name invidious-api-${{ matrix.environment }} \
151+
--region ${{ env.AWS_REGION }}
152+
153+
- name: Get deployment info
154+
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
155+
working-directory: terraform
156+
run: |
157+
echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY
158+
echo "- **Environment**: ${{ matrix.environment }}" >> $GITHUB_STEP_SUMMARY
159+
echo "- **Version**: ${{ steps.meta.outputs.version }}" >> $GITHUB_STEP_SUMMARY
160+
echo "- **Timestamp**: ${{ steps.meta.outputs.timestamp }}" >> $GITHUB_STEP_SUMMARY
161+
echo "" >> $GITHUB_STEP_SUMMARY
162+
echo "### Endpoints" >> $GITHUB_STEP_SUMMARY
163+
echo "- **API Gateway**: $(terraform output -raw api_endpoint)" >> $GITHUB_STEP_SUMMARY
164+
if [ "$(terraform output -raw function_url)" != "null" ]; then
165+
echo "- **Function URL**: $(terraform output -raw function_url)" >> $GITHUB_STEP_SUMMARY
166+
fi
167+
echo "" >> $GITHUB_STEP_SUMMARY
168+
echo "### Resources" >> $GITHUB_STEP_SUMMARY
169+
echo "- **Lambda Function**: $(terraform output -raw lambda_function_name)" >> $GITHUB_STEP_SUMMARY
170+
echo "- **CloudWatch Logs**: [View Logs](https://console.aws.amazon.com/cloudwatch/home?region=${{ env.AWS_REGION }}#logsV2:log-groups/log-group/$(terraform output -raw cloudwatch_log_group))" >> $GITHUB_STEP_SUMMARY

Makefile

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,29 @@ verify:
8181
# TODO
8282

8383

84+
# -----------------------
85+
# Lambda
86+
# -----------------------
87+
88+
lambda-build:
89+
./scripts/build-lambda.sh
90+
91+
lambda-build-push:
92+
./scripts/build-lambda.sh --push --ecr-repo $(ECR_REPO)
93+
94+
lambda-local:
95+
crystal build src/lambda_handler.cr -o bin/bootstrap \
96+
--release --static --no-debug \
97+
-Dapi_only -Dskip_videojs_download
98+
99+
84100
# -----------------------
85101
# Cleaning
86102
# -----------------------
87103

88104
clean:
89-
rm invidious
105+
rm -f invidious
106+
rm -f bin/bootstrap
90107

91108
distclean: clean
92109
rm -rf libs
@@ -109,6 +126,10 @@ help:
109126
@echo " verify Just make sure that the code compiles, but without"
110127
@echo " generating any binaries. Useful to search for errors"
111128
@echo ""
129+
@echo " lambda-build Build Docker image for AWS Lambda"
130+
@echo " lambda-build-push Build and push to ECR (requires ECR_REPO)"
131+
@echo " lambda-local Build Lambda binary locally"
132+
@echo ""
112133
@echo " clean Remove build artifacts"
113134
@echo " distclean Remove build artifacts and libraries"
114135
@echo ""
@@ -126,3 +147,4 @@ help:
126147
# No targets generates an output named after themselves
127148
.PHONY: all get-libs build amd64 run
128149
.PHONY: format test verify clean distclean help
150+
.PHONY: lambda-build lambda-build-push lambda-local

config/lambda.example.yml

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Lambda deployment configuration example
2+
# This configuration is optimized for AWS Lambda API-only mode
3+
4+
# No database configuration needed for API-only mode
5+
# The application will use stub implementations
6+
7+
##
8+
## Server config
9+
##
10+
# Lambda handles the network binding, so these are not used
11+
# but kept for compatibility
12+
host_binding: 0.0.0.0
13+
port: 3000
14+
15+
##
16+
## Logging
17+
##
18+
log_level: Info
19+
colorize_logs: false # Disable color codes in CloudWatch logs
20+
21+
##
22+
## Features
23+
##
24+
# These features require database/frontend, so they're disabled
25+
channel_threads: 0
26+
feed_threads: 0
27+
popular_enabled: false
28+
statistics_enabled: false
29+
login_enabled: false
30+
registration_enabled: false
31+
enable_user_notifications: false
32+
33+
##
34+
## External services
35+
##
36+
# Signature server for YouTube playback (required for some videos)
37+
# Example: https://your-signature-server.example.com
38+
signature_server:
39+
40+
##
41+
## Video player behavior
42+
##
43+
default_user_preferences:
44+
autoplay: false
45+
continue: false
46+
listen: false
47+
local: false
48+
speed: 1.0
49+
quality: hd720
50+
quality_dash: auto
51+
volume: 100
52+
vr_mode: true
53+
54+
##
55+
## API settings
56+
##
57+
# CORS is handled by API Gateway, but these can be used as fallback
58+
disable_proxy: false
59+
force_resolve: ipv4 # Lambda doesn't support IPv6
60+
61+
##
62+
## Pool size (not used in Lambda, but required)
63+
##
64+
pool_size: 1
65+
66+
##
67+
## HMAC key for secure operations
68+
##
69+
# Generate with: pwgen 20 1
70+
hmac_key: "CHANGE_ME_CHANGE_ME_"
71+
72+
##
73+
## Domain configuration (optional)
74+
##
75+
# domain:
76+
# external_port:
77+
# https_only: false
78+
79+
##
80+
## Miscellaneous
81+
##
82+
use_pubsub_feeds: false
83+
use_innertube_for_captions: false
84+
modified_source_code_url: ""
85+
cache_annotations: false

docker/Dockerfile.lambda

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# AWS Lambda Dockerfile for Invidious API-only mode
2+
# This creates a container image compatible with AWS Lambda runtime
3+
4+
# Build stage
5+
FROM crystallang/crystal:1.16.3-alpine AS builder
6+
7+
# Install build dependencies
8+
RUN apk add --no-cache \
9+
sqlite-static \
10+
yaml-static \
11+
libevent-static \
12+
pcre-static \
13+
pcre2-static \
14+
gc-static \
15+
zlib-static \
16+
xz-static \
17+
libxml2-static
18+
19+
WORKDIR /invidious
20+
21+
# Copy dependency files first for better caching
22+
COPY ./shard.yml ./shard.yml
23+
COPY ./shard.lock ./shard.lock
24+
25+
# Install Crystal dependencies
26+
RUN shards install --production
27+
28+
# Copy source code
29+
COPY ./src/ ./src/
30+
31+
# Copy git directory for version information
32+
COPY ./.git/ ./.git/
33+
34+
# Build the Lambda handler as 'bootstrap' (required name for custom runtime)
35+
RUN crystal build ./src/lambda_handler.cr \
36+
-o bootstrap \
37+
--release \
38+
--static \
39+
--no-debug \
40+
-Dapi_only \
41+
-Dskip_videojs_download \
42+
--link-flags "-lxml2 -llzma -lz -lpcre -lpcre2-8 -levent -lgc"
43+
44+
# Runtime stage - using AWS Lambda provided base image
45+
FROM public.ecr.aws/lambda/provided:al2023
46+
47+
# Copy the built binary
48+
COPY --from=builder /invidious/bootstrap ${LAMBDA_RUNTIME_DIR}/
49+
50+
# Copy configuration files and locales
51+
COPY ./config/config.example.yml ${LAMBDA_TASK_ROOT}/config/config.yml
52+
COPY ./locales/ ${LAMBDA_TASK_ROOT}/locales/
53+
54+
# Set proper permissions
55+
RUN chmod 755 ${LAMBDA_RUNTIME_DIR}/bootstrap
56+
57+
# Lambda will automatically execute the 'bootstrap' binary
58+
ENTRYPOINT ["/lambda-entrypoint.sh"]
59+
CMD ["bootstrap"]

0 commit comments

Comments
 (0)