Skip to content

Commit 8ce930e

Browse files
committed
Promote tested Lambda image into prod
1 parent d1efd66 commit 8ce930e

2 files changed

Lines changed: 173 additions & 20 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 140 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,14 @@ permissions:
2121
concurrency: deploy
2222

2323
jobs:
24-
deploy:
25-
strategy:
26-
matrix:
27-
# Define which environments you want to deploy
28-
# Environments are setup in GutHub
29-
environment: [ stage-us-east, prod-us-east ]
30-
runs-on: ${{ format('codebuild-java-cdk-serverless-clamscan-{0}-runner-{1}-{2}', matrix.environment, github.run_id, github.run_attempt) }}
31-
environment: ${{ matrix.environment }}
32-
33-
24+
deploy-stage:
25+
name: deploy (stage-us-east)
26+
runs-on: ${{ format('codebuild-java-cdk-serverless-clamscan-stage-us-east-runner-{0}-{1}', github.run_id, github.run_attempt) }}
27+
environment: stage-us-east
28+
outputs:
29+
image_manifest_b64: ${{ steps.capture-image.outputs.image_manifest_b64 }}
30+
image_manifest_media_type: ${{ steps.capture-image.outputs.image_manifest_media_type }}
31+
3432
steps:
3533
- name: Checkout Code
3634
uses: actions/checkout@v6
@@ -114,6 +112,53 @@ jobs:
114112
sleep 30
115113
echo "Running Virus Scan Validation Tests..."
116114
mvn --batch-mode --no-transfer-progress exec:java
115+
116+
- name: Capture Tested Image Manifest For Promotion
117+
id: capture-image
118+
working-directory: ./cdk
119+
run: |
120+
FUNCTION_NAME=$(aws cloudformation describe-stacks \
121+
--stack-name ClamavLambdaStack \
122+
--query "Stacks[0].Outputs[?OutputKey=='ClamavLambdaStackLambdaName'].OutputValue" \
123+
--output text)
124+
125+
RESOLVED_IMAGE_URI=$(aws lambda get-function \
126+
--function-name "$FUNCTION_NAME" \
127+
--query 'Code.ResolvedImageUri' \
128+
--output text)
129+
130+
REPOSITORY_NAME="${RESOLVED_IMAGE_URI%@*}"
131+
REPOSITORY_NAME="${REPOSITORY_NAME#*.amazonaws.com/}"
132+
IMAGE_DIGEST="${RESOLVED_IMAGE_URI#*@}"
133+
134+
IMAGE_MANIFEST=$(aws ecr batch-get-image \
135+
--repository-name "$REPOSITORY_NAME" \
136+
--image-ids imageDigest="$IMAGE_DIGEST" \
137+
--accepted-media-types \
138+
application/vnd.docker.distribution.manifest.v2+json \
139+
application/vnd.oci.image.manifest.v1+json \
140+
--query 'images[0].imageManifest' \
141+
--output text)
142+
143+
IMAGE_MANIFEST_MEDIA_TYPE=$(aws ecr batch-get-image \
144+
--repository-name "$REPOSITORY_NAME" \
145+
--image-ids imageDigest="$IMAGE_DIGEST" \
146+
--accepted-media-types \
147+
application/vnd.docker.distribution.manifest.v2+json \
148+
application/vnd.oci.image.manifest.v1+json \
149+
--query 'images[0].imageManifestMediaType' \
150+
--output text)
151+
152+
if [ -z "$IMAGE_MANIFEST_MEDIA_TYPE" ] || [ "$IMAGE_MANIFEST_MEDIA_TYPE" = "None" ]; then
153+
IMAGE_MANIFEST_MEDIA_TYPE="application/vnd.docker.distribution.manifest.v2+json"
154+
fi
155+
156+
IMAGE_MANIFEST_B64=$(printf '%s' "$IMAGE_MANIFEST" | base64 -w 0)
157+
158+
{
159+
echo "image_manifest_b64=$IMAGE_MANIFEST_B64"
160+
echo "image_manifest_media_type=$IMAGE_MANIFEST_MEDIA_TYPE"
161+
} >> "$GITHUB_OUTPUT"
117162
118163
- name: Rollback Lambda Alias if Tests Fail
119164
if: failure() && steps.scan-tests.outcome == 'failure'
@@ -135,3 +180,88 @@ jobs:
135180
--function-name "$FUNCTION_NAME" \
136181
--name "$ALIAS_NAME" \
137182
--function-version "$PREV_VERSION"
183+
184+
deploy-prod:
185+
name: deploy (prod-us-east)
186+
needs: deploy-stage
187+
runs-on: ubuntu-latest
188+
environment: prod-us-east
189+
190+
steps:
191+
- name: Checkout Code
192+
uses: actions/checkout@v6
193+
194+
- name: Set up JDK 21
195+
uses: actions/setup-java@v5
196+
with:
197+
java-version: '21'
198+
distribution: 'corretto'
199+
cache: maven
200+
201+
- name: Install the latest AWS CDK
202+
run: |
203+
npm install -g aws-cdk
204+
echo "Node Version: $(node -v)"
205+
echo "CDK Version: $(cdk version)"
206+
207+
- name: Setup AWS Credentials
208+
id: aws-creds
209+
uses: aws-actions/configure-aws-credentials@v6
210+
with:
211+
aws-region: ${{ vars.AWS_REGION || 'us-east-1' }}
212+
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
213+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
214+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
215+
mask-aws-account-id: true
216+
217+
- name: Add AWS_ACCOUNT_ID to Environment
218+
run: echo "AWS_ACCOUNT_ID=${{ steps.aws-creds.outputs.aws-account-id }}" >> $GITHUB_ENV
219+
220+
- name: Build CDK project without rebuilding the Lambda image
221+
run: >
222+
mvn -pl shared-model,cdk -am install -DskipTests
223+
--no-transfer-progress --quiet
224+
225+
- name: Construct bucketNames Context
226+
run: |
227+
echo "BUCKET_NAMES=${{ vars.S3_BUCKET_NAMES }}" >> $GITHUB_ENV
228+
echo "Final list of bucket names to deploy against: ${{ vars.S3_BUCKET_NAMES }}"
229+
230+
- name: Ensure CDK is bootstraped and up to date
231+
working-directory: ./cdk
232+
run: cdk bootstrap --ci=true aws://${AWS_ACCOUNT_ID}/${{ vars.AWS_REGION || 'us-east-1' }}
233+
234+
- name: Promote Tested Image Into Production ECR
235+
run: |
236+
TARGET_REPOSITORY_NAME="cdk-hnb659fds-container-assets-${AWS_ACCOUNT_ID}-${{ vars.AWS_REGION || 'us-east-1' }}"
237+
PROMOTED_IMAGE_TAG="promoted-${GITHUB_SHA}"
238+
239+
printf '%s' '${{ needs.deploy-stage.outputs.image_manifest_b64 }}' | base64 -d > /tmp/promoted-image-manifest.json
240+
IMAGE_MANIFEST="$(tr -d '\n' < /tmp/promoted-image-manifest.json)"
241+
242+
aws ecr put-image \
243+
--repository-name "$TARGET_REPOSITORY_NAME" \
244+
--image-manifest "$IMAGE_MANIFEST" \
245+
--image-manifest-media-type "${{ needs.deploy-stage.outputs.image_manifest_media_type }}" \
246+
--image-tag "$PROMOTED_IMAGE_TAG"
247+
248+
PROMOTED_IMAGE_DIGEST=$(aws ecr describe-images \
249+
--repository-name "$TARGET_REPOSITORY_NAME" \
250+
--image-ids imageTag="$PROMOTED_IMAGE_TAG" \
251+
--query 'imageDetails[0].imageDigest' \
252+
--output text)
253+
254+
echo "PROMOTED_IMAGE_REPOSITORY_NAME=$TARGET_REPOSITORY_NAME" >> $GITHUB_ENV
255+
echo "PROMOTED_IMAGE_DIGEST=$PROMOTED_IMAGE_DIGEST" >> $GITHUB_ENV
256+
257+
- name: Deploy CDK Stack Using Promoted Image
258+
working-directory: ./cdk
259+
env:
260+
ONLY_TAG_INFECTED: ${{ vars.ONLY_TAG_INFECTED }}
261+
VALIDATION_BUCKET: ${{ vars.VALIDATION_BUCKET }}
262+
run: >
263+
cdk deploy --require-approval=never --ci=true
264+
--context bucketNames="${{ env.BUCKET_NAMES }}"
265+
--context addBucketPolicy="${{ vars.ADD_BUCKET_POLICY || 'false' }}"
266+
--context imageRepositoryName="${{ env.PROMOTED_IMAGE_REPOSITORY_NAME }}"
267+
--context imageTagOrDigest="${{ env.PROMOTED_IMAGE_DIGEST }}"

cdk/src/main/java/cloud/cleo/clamav/cdk/ClamavLambdaStack.java

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import software.amazon.awscdk.Size;
1313
import software.amazon.awscdk.Stack;
1414
import software.amazon.awscdk.StackProps;
15+
import software.amazon.awscdk.services.ecr.IRepository;
16+
import software.amazon.awscdk.services.ecr.Repository;
1517
import software.amazon.awscdk.services.ecr.assets.DockerImageAsset;
1618
import software.amazon.awscdk.services.ecr.assets.Platform;
1719
import software.amazon.awscdk.services.iam.AnyPrincipal;
@@ -81,14 +83,7 @@ public ClamavLambdaStack(final Construct scope, final String id, final StackProp
8183
}
8284
}
8385

84-
// Build the Docker image asset.
85-
// The bundling step runs a Maven build using a Maven image that supports Java 21,
86-
// then copies the produced JAR into the asset output so that the Dockerfile COPY
87-
// instruction (which expects target/lambda.jar) works properly.
88-
DockerImageAsset imageAsset = DockerImageAsset.Builder.create(this, "ClamavLambdaImage")
89-
.platform(isCloudShell() ? Platform.LINUX_AMD64 : Platform.LINUX_ARM64)
90-
.directory(".")
91-
.build();
86+
DockerImageCode lambdaCode = getLambdaCode();
9287

9388
// Create custom log group first
9489
LogGroup customLogGroup = LogGroup.Builder.create(this, LAMBDA_NAME + "LogGroup")
@@ -99,8 +94,7 @@ public ClamavLambdaStack(final Construct scope, final String id, final StackProp
9994

10095
// Create a Docker-based Lambda function using the built image.
10196
DockerImageFunction lambdaFunction = DockerImageFunction.Builder.create(this, LAMBDA_NAME)
102-
.code(DockerImageCode.fromEcr(imageAsset.getRepository(),
103-
EcrImageCodeProps.builder().tagOrDigest(imageAsset.getImageTag()).build()))
97+
.code(lambdaCode)
10498
//
10599
// We use ARM because its cheaper for CPU bound executions like CLamAV scanning
106100
.architecture(isCloudShell() ? Architecture.X86_64 : Architecture.ARM_64)
@@ -220,6 +214,35 @@ private boolean getContextBoolean(String key, boolean defaultValue) {
220214
return defaultValue;
221215
}
222216

217+
private String getContextString(String key) {
218+
Object contextValue = this.getNode().tryGetContext(key);
219+
if (contextValue instanceof String str) {
220+
String trimmed = str.trim();
221+
return trimmed.isEmpty() ? null : trimmed;
222+
}
223+
return null;
224+
}
225+
226+
private DockerImageCode getLambdaCode() {
227+
String imageRepositoryName = getContextString("imageRepositoryName");
228+
String imageTagOrDigest = getContextString("imageTagOrDigest");
229+
230+
if (imageRepositoryName != null && imageTagOrDigest != null) {
231+
IRepository repository = Repository.fromRepositoryName(this, "ClamavLambdaImageRepository",
232+
imageRepositoryName);
233+
return DockerImageCode.fromEcr(repository,
234+
EcrImageCodeProps.builder().tagOrDigest(imageTagOrDigest).build());
235+
}
236+
237+
DockerImageAsset imageAsset = DockerImageAsset.Builder.create(this, "ClamavLambdaImage")
238+
.platform(isCloudShell() ? Platform.LINUX_AMD64 : Platform.LINUX_ARM64)
239+
.directory(".")
240+
.build();
241+
242+
return DockerImageCode.fromEcr(imageAsset.getRepository(),
243+
EcrImageCodeProps.builder().tagOrDigest(imageAsset.getImageTag()).build());
244+
}
245+
223246
/**
224247
* Detect if using CloudShell which means we need x86 architecture/platform.
225248
*

0 commit comments

Comments
 (0)