feat: simplify blog lambda deployment pipeline
This commit is contained in:
parent
04237038fe
commit
a59a8e6461
3 changed files with 166 additions and 23 deletions
|
|
@ -6,6 +6,7 @@ phases:
|
||||||
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ECR_REPOSITORY_URI
|
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ECR_REPOSITORY_URI
|
||||||
- IMAGE_TAG=$(date +%s)
|
- IMAGE_TAG=$(date +%s)
|
||||||
- echo "Image tag will be ${IMAGE_TAG}"
|
- echo "Image tag will be ${IMAGE_TAG}"
|
||||||
|
- if [ -z "${ECR_REPOSITORY_NAME:-}" ]; then ECR_REPOSITORY_NAME="${ECR_REPOSITORY_URI##*/}"; fi
|
||||||
build:
|
build:
|
||||||
commands:
|
commands:
|
||||||
- echo Build started on `date`
|
- echo Build started on `date`
|
||||||
|
|
@ -18,3 +19,10 @@ phases:
|
||||||
- docker push $ECR_REPOSITORY_URI:$IMAGE_TAG
|
- docker push $ECR_REPOSITORY_URI:$IMAGE_TAG
|
||||||
- docker push $ECR_REPOSITORY_URI:latest
|
- docker push $ECR_REPOSITORY_URI:latest
|
||||||
- echo "Image pushed with tags ${IMAGE_TAG} and latest"
|
- echo "Image pushed with tags ${IMAGE_TAG} and latest"
|
||||||
|
- IMAGE_DIGEST=$(aws ecr describe-images --repository-name "$ECR_REPOSITORY_NAME" --image-ids imageTag="$IMAGE_TAG" --query "imageDetails[0].imageDigest" --output text)
|
||||||
|
- echo "IMAGE_TAG=${IMAGE_TAG}" > image-details.env
|
||||||
|
- echo "IMAGE_DIGEST=${IMAGE_DIGEST}" >> image-details.env
|
||||||
|
|
||||||
|
artifacts:
|
||||||
|
files:
|
||||||
|
- image-details.env
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,11 @@ Parameters:
|
||||||
Default: blog-lambda-source.zip
|
Default: blog-lambda-source.zip
|
||||||
Description: S3 object key for source code archive
|
Description: S3 object key for source code archive
|
||||||
|
|
||||||
|
LambdaFunctionName:
|
||||||
|
Type: String
|
||||||
|
Default: blog-deployment-webhook-handler
|
||||||
|
Description: Lambda function name that receives updated images
|
||||||
|
|
||||||
Resources:
|
Resources:
|
||||||
|
|
||||||
SourceBucket:
|
SourceBucket:
|
||||||
|
|
@ -59,8 +64,13 @@ Resources:
|
||||||
- ecr:UploadLayerPart
|
- ecr:UploadLayerPart
|
||||||
- ecr:CompleteLayerUpload
|
- ecr:CompleteLayerUpload
|
||||||
- ecr:PutImage
|
- ecr:PutImage
|
||||||
|
- ecr:DescribeImages
|
||||||
Resource:
|
Resource:
|
||||||
Fn::ImportValue: BlogDeployment-RepositoryArn
|
Fn::ImportValue: BlogDeployment-RepositoryArn
|
||||||
|
- Effect: Allow
|
||||||
|
Action:
|
||||||
|
- lambda:UpdateFunctionCode
|
||||||
|
Resource: "*"
|
||||||
- Effect: Allow
|
- Effect: Allow
|
||||||
Action:
|
Action:
|
||||||
- s3:GetObject
|
- s3:GetObject
|
||||||
|
|
@ -92,11 +102,138 @@ Resources:
|
||||||
Value: !Ref AWS::Region
|
Value: !Ref AWS::Region
|
||||||
- Name: AWS_ACCOUNT_ID
|
- Name: AWS_ACCOUNT_ID
|
||||||
Value: !Ref AWS::AccountId
|
Value: !Ref AWS::AccountId
|
||||||
|
- Name: ECR_REPOSITORY_NAME
|
||||||
|
Value:
|
||||||
|
Fn::ImportValue: BlogDeployment-RepositoryName
|
||||||
Source:
|
Source:
|
||||||
Type: CODEPIPELINE
|
Type: CODEPIPELINE
|
||||||
BuildSpec: ci/buildspec.yml
|
BuildSpec: ci/buildspec.yml
|
||||||
TimeoutInMinutes: 30
|
TimeoutInMinutes: 30
|
||||||
|
|
||||||
|
PipelineDeployLambdaRole:
|
||||||
|
Type: AWS::IAM::Role
|
||||||
|
Properties:
|
||||||
|
AssumeRolePolicyDocument:
|
||||||
|
Version: '2012-10-17'
|
||||||
|
Statement:
|
||||||
|
- Effect: Allow
|
||||||
|
Principal:
|
||||||
|
Service: lambda.amazonaws.com
|
||||||
|
Action: sts:AssumeRole
|
||||||
|
Policies:
|
||||||
|
- PolicyName: PipelineDeployPermissions
|
||||||
|
PolicyDocument:
|
||||||
|
Version: '2012-10-17'
|
||||||
|
Statement:
|
||||||
|
- Effect: Allow
|
||||||
|
Action:
|
||||||
|
- logs:CreateLogGroup
|
||||||
|
- logs:CreateLogStream
|
||||||
|
- logs:PutLogEvents
|
||||||
|
Resource: '*'
|
||||||
|
- Effect: Allow
|
||||||
|
Action:
|
||||||
|
- s3:GetObject
|
||||||
|
Resource:
|
||||||
|
- !Sub "arn:aws:s3:::codebuild-${AWS::Region}-${AWS::AccountId}-input-bucket/*"
|
||||||
|
- Effect: Allow
|
||||||
|
Action:
|
||||||
|
- lambda:UpdateFunctionCode
|
||||||
|
Resource: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}"
|
||||||
|
- Effect: Allow
|
||||||
|
Action:
|
||||||
|
- codepipeline:PutJobSuccessResult
|
||||||
|
- codepipeline:PutJobFailureResult
|
||||||
|
Resource: '*'
|
||||||
|
|
||||||
|
PipelineDeployLambda:
|
||||||
|
Type: AWS::Lambda::Function
|
||||||
|
Properties:
|
||||||
|
FunctionName: blog-lambda-image-deployer
|
||||||
|
Runtime: python3.13
|
||||||
|
Handler: index.handler
|
||||||
|
Role: !GetAtt PipelineDeployLambdaRole.Arn
|
||||||
|
Timeout: 60
|
||||||
|
MemorySize: 256
|
||||||
|
Environment:
|
||||||
|
Variables:
|
||||||
|
TARGET_FUNCTION_NAME: !Ref LambdaFunctionName
|
||||||
|
REPOSITORY_URI:
|
||||||
|
Fn::ImportValue: BlogDeployment-RepositoryUri
|
||||||
|
Code:
|
||||||
|
ZipFile: |
|
||||||
|
import boto3
|
||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
import tempfile
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger()
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
codepipeline = boto3.client('codepipeline')
|
||||||
|
lambda_client = boto3.client('lambda')
|
||||||
|
|
||||||
|
def handler(event, context):
|
||||||
|
job = event['CodePipeline.job']
|
||||||
|
job_id = job['id']
|
||||||
|
job_data = job['data']
|
||||||
|
temp_path = None
|
||||||
|
try:
|
||||||
|
temp_fd, temp_path = tempfile.mkstemp()
|
||||||
|
os.close(temp_fd)
|
||||||
|
_download_artifact(job_data, temp_path)
|
||||||
|
metadata = _extract_metadata(temp_path)
|
||||||
|
digest = metadata.get('IMAGE_DIGEST')
|
||||||
|
if not digest:
|
||||||
|
raise ValueError('IMAGE_DIGEST not found in artifact')
|
||||||
|
image_uri = f"{os.environ['REPOSITORY_URI']}@{digest}"
|
||||||
|
lambda_client.update_function_code(
|
||||||
|
FunctionName=os.environ['TARGET_FUNCTION_NAME'],
|
||||||
|
ImageUri=image_uri
|
||||||
|
)
|
||||||
|
codepipeline.put_job_success_result(jobId=job_id)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception('Lambda image update failed')
|
||||||
|
codepipeline.put_job_failure_result(
|
||||||
|
jobId=job_id,
|
||||||
|
failureDetails={
|
||||||
|
'type': 'JobFailed',
|
||||||
|
'message': str(exc)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if temp_path and os.path.exists(temp_path):
|
||||||
|
os.remove(temp_path)
|
||||||
|
|
||||||
|
def _download_artifact(job_data, destination):
|
||||||
|
artifact = job_data['inputArtifacts'][0]
|
||||||
|
creds = job_data['artifactCredentials']
|
||||||
|
session = boto3.session.Session(
|
||||||
|
aws_access_key_id=creds['accessKeyId'],
|
||||||
|
aws_secret_access_key=creds['secretAccessKey'],
|
||||||
|
aws_session_token=creds['sessionToken'],
|
||||||
|
)
|
||||||
|
s3 = session.client('s3', region_name=os.environ['AWS_REGION'])
|
||||||
|
location = artifact['location']['s3Location']
|
||||||
|
s3.download_file(location['bucketName'], location['objectKey'], destination)
|
||||||
|
|
||||||
|
def _extract_metadata(archive_path):
|
||||||
|
with zipfile.ZipFile(archive_path) as archive:
|
||||||
|
target = next((n for n in archive.namelist() if n.endswith('image-details.env')), None)
|
||||||
|
if not target:
|
||||||
|
raise FileNotFoundError('image-details.env missing from artifact')
|
||||||
|
with archive.open(target) as handle:
|
||||||
|
content = handle.read().decode()
|
||||||
|
result = {}
|
||||||
|
for line in content.splitlines():
|
||||||
|
if '=' in line:
|
||||||
|
key, value = line.split('=', 1)
|
||||||
|
result[key.strip()] = value.strip()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
CodePipelineRole:
|
CodePipelineRole:
|
||||||
Type: AWS::IAM::Role
|
Type: AWS::IAM::Role
|
||||||
Properties:
|
Properties:
|
||||||
|
|
@ -135,6 +272,10 @@ Resources:
|
||||||
Resource:
|
Resource:
|
||||||
- !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:build/*"
|
- !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:build/*"
|
||||||
- !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/*"
|
- !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/*"
|
||||||
|
- Effect: Allow
|
||||||
|
Action:
|
||||||
|
- lambda:InvokeFunction
|
||||||
|
Resource: !GetAtt PipelineDeployLambda.Arn
|
||||||
- Effect: Allow
|
- Effect: Allow
|
||||||
Action:
|
Action:
|
||||||
- codepipeline:PutApprovalResult
|
- codepipeline:PutApprovalResult
|
||||||
|
|
@ -175,8 +316,22 @@ Resources:
|
||||||
Version: "1"
|
Version: "1"
|
||||||
InputArtifacts:
|
InputArtifacts:
|
||||||
- Name: SourceOutput
|
- Name: SourceOutput
|
||||||
|
OutputArtifacts:
|
||||||
|
- Name: ImageDetails
|
||||||
Configuration:
|
Configuration:
|
||||||
ProjectName: !Ref BlogLambdaBuildProject
|
ProjectName: !Ref BlogLambdaBuildProject
|
||||||
|
- Name: Deploy
|
||||||
|
Actions:
|
||||||
|
- Name: UpdateLambdaImage
|
||||||
|
ActionTypeId:
|
||||||
|
Category: Invoke
|
||||||
|
Owner: AWS
|
||||||
|
Provider: Lambda
|
||||||
|
Version: "1"
|
||||||
|
InputArtifacts:
|
||||||
|
- Name: ImageDetails
|
||||||
|
Configuration:
|
||||||
|
FunctionName: !Ref PipelineDeployLambda
|
||||||
|
|
||||||
S3SourceChangeRule:
|
S3SourceChangeRule:
|
||||||
Type: AWS::Events::Rule
|
Type: AWS::Events::Rule
|
||||||
|
|
@ -225,9 +380,3 @@ Outputs:
|
||||||
Value: !Ref SourceBucket
|
Value: !Ref SourceBucket
|
||||||
Export:
|
Export:
|
||||||
Name: !Sub "${AWS::StackName}-SourceBucket"
|
Name: !Sub "${AWS::StackName}-SourceBucket"
|
||||||
|
|
||||||
PipelineName:
|
|
||||||
Description: CodePipeline name
|
|
||||||
Value: !Ref BlogLambdaPipeline
|
|
||||||
Export:
|
|
||||||
Name: !Sub "${AWS::StackName}-PipelineName"
|
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,6 @@ Parameters:
|
||||||
Default: main
|
Default: main
|
||||||
Description: Git repository branch
|
Description: Git repository branch
|
||||||
|
|
||||||
ImageDigest:
|
|
||||||
Type: String
|
|
||||||
Default: ""
|
|
||||||
Description: "ECR image digest (e.g., sha256:abc123...). If empty, uses 'latest' tag. Use digest for deterministic deployments."
|
|
||||||
|
|
||||||
Conditions:
|
|
||||||
UseDigest: !Not [!Equals [!Ref ImageDigest, ""]]
|
|
||||||
|
|
||||||
Resources:
|
Resources:
|
||||||
|
|
||||||
MyLambdaRole:
|
MyLambdaRole:
|
||||||
|
|
@ -79,13 +71,7 @@ Resources:
|
||||||
Properties:
|
Properties:
|
||||||
FunctionName: blog-deployment-webhook-handler
|
FunctionName: blog-deployment-webhook-handler
|
||||||
PackageType: Image
|
PackageType: Image
|
||||||
ImageUri: !If
|
ImageUri: !Sub
|
||||||
- UseDigest
|
|
||||||
- !Sub
|
|
||||||
- "${RepoUri}@${Digest}"
|
|
||||||
- RepoUri: !ImportValue BlogDeployment-RepositoryUri
|
|
||||||
Digest: !Ref ImageDigest
|
|
||||||
- !Sub
|
|
||||||
- "${RepoUri}:latest"
|
- "${RepoUri}:latest"
|
||||||
- RepoUri: !ImportValue BlogDeployment-RepositoryUri
|
- RepoUri: !ImportValue BlogDeployment-RepositoryUri
|
||||||
Timeout: 300
|
Timeout: 300
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue