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
|
||||
- IMAGE_TAG=$(date +%s)
|
||||
- echo "Image tag will be ${IMAGE_TAG}"
|
||||
- if [ -z "${ECR_REPOSITORY_NAME:-}" ]; then ECR_REPOSITORY_NAME="${ECR_REPOSITORY_URI##*/}"; fi
|
||||
build:
|
||||
commands:
|
||||
- echo Build started on `date`
|
||||
|
|
@ -18,3 +19,10 @@ phases:
|
|||
- docker push $ECR_REPOSITORY_URI:$IMAGE_TAG
|
||||
- docker push $ECR_REPOSITORY_URI: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
|
||||
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:
|
||||
|
||||
SourceBucket:
|
||||
|
|
@ -59,8 +64,13 @@ Resources:
|
|||
- ecr:UploadLayerPart
|
||||
- ecr:CompleteLayerUpload
|
||||
- ecr:PutImage
|
||||
- ecr:DescribeImages
|
||||
Resource:
|
||||
Fn::ImportValue: BlogDeployment-RepositoryArn
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- lambda:UpdateFunctionCode
|
||||
Resource: "*"
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- s3:GetObject
|
||||
|
|
@ -92,11 +102,138 @@ Resources:
|
|||
Value: !Ref AWS::Region
|
||||
- Name: AWS_ACCOUNT_ID
|
||||
Value: !Ref AWS::AccountId
|
||||
- Name: ECR_REPOSITORY_NAME
|
||||
Value:
|
||||
Fn::ImportValue: BlogDeployment-RepositoryName
|
||||
Source:
|
||||
Type: CODEPIPELINE
|
||||
BuildSpec: ci/buildspec.yml
|
||||
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:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
|
|
@ -135,6 +272,10 @@ Resources:
|
|||
Resource:
|
||||
- !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:build/*"
|
||||
- !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/*"
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- lambda:InvokeFunction
|
||||
Resource: !GetAtt PipelineDeployLambda.Arn
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- codepipeline:PutApprovalResult
|
||||
|
|
@ -175,8 +316,22 @@ Resources:
|
|||
Version: "1"
|
||||
InputArtifacts:
|
||||
- Name: SourceOutput
|
||||
OutputArtifacts:
|
||||
- Name: ImageDetails
|
||||
Configuration:
|
||||
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:
|
||||
Type: AWS::Events::Rule
|
||||
|
|
@ -225,9 +380,3 @@ Outputs:
|
|||
Value: !Ref SourceBucket
|
||||
Export:
|
||||
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
|
||||
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:
|
||||
|
||||
MyLambdaRole:
|
||||
|
|
@ -79,15 +71,9 @@ Resources:
|
|||
Properties:
|
||||
FunctionName: blog-deployment-webhook-handler
|
||||
PackageType: Image
|
||||
ImageUri: !If
|
||||
- UseDigest
|
||||
- !Sub
|
||||
- "${RepoUri}@${Digest}"
|
||||
- RepoUri: !ImportValue BlogDeployment-RepositoryUri
|
||||
Digest: !Ref ImageDigest
|
||||
- !Sub
|
||||
- "${RepoUri}:latest"
|
||||
- RepoUri: !ImportValue BlogDeployment-RepositoryUri
|
||||
ImageUri: !Sub
|
||||
- "${RepoUri}:latest"
|
||||
- RepoUri: !ImportValue BlogDeployment-RepositoryUri
|
||||
Timeout: 300
|
||||
MemorySize: 512
|
||||
Architectures:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue