How can I remove unwanted files in an S3 bucket as the output of a pipeline in CodePipeline, using CodeBuild's buildspec.yml file?
For example:
The build folder of a GitHub repo is put in the designated S3 bucket so the bucket can be used as a static website.
I pushed a file earlier to the bucket which I don't need anymore. How do I use the buildspec.yml file to "clean" the bucket before pushing the artifacts of my pipeline to the bucket?
An example buildspec.yml file:
version: 0.2
phases:
build:
commands:
- mkdir build-output
- find . -type d -name public -exec cp -R {} build-output \;
- find . -mindepth 1 -name build-output -prune -o -exec rm -rf {} +
post_build:
commands:
- mv build-output/**/* ./
- mv build-output/* ./
- rm -R build-output
artifacts:
files:
- '**/*'
Should the command:
rm -rf *
in build phase like this?
build:
commands:
- aws s3 rm s3://mybucket/ --recursive
And how do I reference the right bucket instead of hardcoding the name in the file?
To delete the files in the S3 bucket, you can use the aws s3 rm --recursive command as you already alluded to.
You can pass in the bucket name from the pipeline to CodeBuild by setting it in the environment variable.
ArtifactsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: my-artifacts
CodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Environment:
EnvironmentVariables:
- Name: ARTIFACTS_BUCKET
Value: !Ref ArtifactsBucket
Type: PLAINTEXT
In the buildspec, you can then refer to the ARTIFACTS_BUCKET env var, for example:
build:
commands:
- aws s3 rm --recursive "s3://${ARTIFACTS_BUCKET}/"
An alternative approach you could take is to declare lifecycle management on the bucket. For example, you can say "delete all objects after 30 days" like so:
ArtifactsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: my-artifacts
LifecycleConfiguration:
Rules:
- ExpirationInDays: 30
Id: Expire objects in 30 days
Status: Enabled
Related
I tried many combination, none worked. The latest was:
image: node:14
before_script:
- apk add zip
stages:
- build
build:
stage: build
script:
- echo "Building Lambda function..."
- echo "console.log('Hello World');" > index.js
- zip index.zip index.js
- echo "Deploying Lambda function to AWS..."
- aws configure set aws_access_key_id [AWS_ACCESS_KEY_ID]
- aws configure set aws_secret_access_key [AWS_SECRET_ACCESS_KEY]
- aws lambda create-function --function-name video-promotion --runtime nodejs14 --handler index.handler --zip-file fileb://index.zip
I am new to YAML file. I want to append Timestamp to S3 bucket folder every time so that each build will be unique. In the post_build I append timestamp to S3 bucket as follows. When the codepipeline is triggered all files are stored to S3 bucket Inhouse folder but folder with timestamp is not getting generated. s3://${S3_BUCKET}/Inhouse/${'date'}
Version: 0.2
env:
variables:
S3_BUCKET: Inhouse-market-dev
phases:
install:
runtime-versions:
nodejs: 10
commands:
- npm install
- npm install -g #angular/cli
build:
commands:
- echo Build started on `date`
post_build:
commands:
- aws s3 cp . s3://${S3_BUCKET}/Inhouse/${'date'} --recursive --acl public-read --cache-control "max-age=${CACHE_CONTROL}"
- echo Build completed on `date`
I think your use of ${'date'} is incorrect. I would recommend trying the following to actually get the unix timestamp:
post_build:
commands:
- current_timestamp=$(date +"%s")
- aws s3 cp . s3://${S3_BUCKET}/Inhouse/${current_timestamp} --recursive --acl public-read --cache-control "max-age=${CACHE_CONTROL}"
- echo Build completed on `date` which is ${current_timestamp}
I have 2 AWS accounts. Lets say A and B.
Account A uses CodeBuild to build and upload artifacts to an S3 bucket owned by B. B account has set a ACL permission for the bucket in order to give Write permissions to A.
The artifact file is successfully uploaded to the S3 bucket. However, B account doesnt have any permission over the file, since the file is owned by A.
Account A can change the ownership by running
aws s3api put-object-acl --bucket bucket-name --key key-name --acl bucket-owner-full-control
But this has to be manually run after every build from A account. How can I grant permissions to account B through CodeBuild procedure? Or how can account B override this ownership permission error.
The CodeBuild starts automatically with web-hooks and my buildspec is this:
version: 0.2
env:
phases:
install:
runtime-versions:
java: openjdk8
commands:
- echo Entered the install phase...
build:
commands:
- echo Entered the build phase...
post_build:
commands:
- echo Entered the post_build phase...
artifacts:
files:
- 'myFile.txt'
CodeBuild does not natively support writing artifact to a different account as it does not set proper ACL on the cross account object. This is the reason the following limitation is called out in the CodePipeline documentation:
Cross-account actions are not supported for the following action types:
Jenkins build actions
CodeBuild build or test actions
https://docs.aws.amazon.com/codepipeline/latest/userguide/pipelines-create-cross-account.html
One workaround is setting the ACL on the artifact yourself in the CodeBuild:
version: 0.2
phases:
post_build:
commands:
- aws s3api list-objects --bucket testingbucket --prefix CFNtest/OutputArti >> $CODEBUILD_SRC_DIR/objects.json
- |
for i in $(jq -r '.Contents[]|.Key' $CODEBUILD_SRC_DIR/objects.json); do
echo $i
aws s3api put-object-acl --bucket testingbucket --key $i --acl bucket-owner-full-control
done
I did it using aws cli commands from the build phase.
version: 0.2
phases:
build:
commands:
- mvn install...
- aws s3 cp my-file s3://bucketName --acl bucket-owner-full-control
I am using the build phase, since post_build will be executed even if the build was not successful.
edit: updated answer with a sample.
We have some flaky CodeDeploy errors that are frustrating. In about 10% of our deploys we get the following error : An AppSpec file is required, but could not be found in the revision.
The problem is that when we download the artifact zip file from s3 we clearly see a appspec.yaml file. Our build script doesn't change between deploys and when we rerun the pipeline on the same commit (using the "Release change" button), without changing anything, it works.
The error message isn't helpful and it seems like CodeDeploy isn't 100% reliable.
We use ECS Fargate using Blue/Green Deployment.
Our buildspec.yml file looks like this:
version: 0.2
env:
parameter-store:
BUILD_ENV: key-foo-site-node-env
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)
- IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-6)
- ACCOUNT_ID=$(aws sts get-caller-identity --output text --query 'Account')
- REPOSITORY_URI="$ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/foo-site"
- echo Saving source version into version.txt...
- echo $IMAGE_TAG >> version.txt
build:
commands:
- echo Build started on `date`
- echo Building the app Docker image...
- docker build -t $REPOSITORY_URI/app:$IMAGE_TAG .
- echo Building the nginx Docker image...
- docker build -t $REPOSITORY_URI/nginx:$IMAGE_TAG docker/nginx
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker images...
- docker push $REPOSITORY_URI/app:$IMAGE_TAG
- docker push $REPOSITORY_URI/nginx:$IMAGE_TAG
# Create a valid json file that will be used to create a new task definition version
# Using sed we need to replace $APP_IMAGE and $NGINX_IMAGE by image urls
- echo Creating a task definition json
- sed "s+\$APP_IMAGE+$REPOSITORY_URI/app:$IMAGE_TAG+g; s+\$NGINX_IMAGE+$REPOSITORY_URI/nginx:$IMAGE_TAG+g;" taskdef.$BUILD_ENV.json > register-task-definition.json
# Using the aws cli we register a new task definition
# We need to new task definition arn to create a valid appspec.yaml
# If you need debugging, the next line is useful
# - aws --debug ecs register-task-definition --cli-input-json "$(cat register-task-definition.json)" > task-definition.json
- echo Creating an appspec.yaml file
- TASK_DEFINITION_ARN=`aws ecs register-task-definition --cli-input-json "$(cat register-task-definition.json)" --query 'taskDefinition.taskDefinitionArn' --output text`
- sed "s+\$TASK_DEFINITION_ARN+$TASK_DEFINITION_ARN+g" appspec.yml > appspec.yaml
artifacts:
files:
- appspec.yaml
- register-task-definition.json
- task-definition.json
Our appspec.yml file looks like this:
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: "$TASK_DEFINITION_ARN"
LoadBalancerInfo:
ContainerName: "nginx"
ContainerPort: "80"
Probably not relevant anymore, but it looks like your AppSpec file has a different suffix (.yml) than indicated in the artifcats definition of the buildspec file (.yaml).
I am trying to build the most simple of Lambda functions in Go using AWS CodePipeline. Despite playing with it for about 2 weeks I still haven't managed to get it deployed.
main.go
package main
import (
"context"
"github.com/aws/aws-lambda-go/lambda"
)
func HandleRequest(ctx context.Context) (string, error) {
return "Hello from Go!", nil
}
func main() {
lambda.Start(HandleRequest)
}
buildspec.yml
version: 0.2
env:
variables:
S3_BUCKET: dlp-queuetime
PACKAGE: dlp-queuetime-fetcher
phases:
install:
runtime-versions:
golang: 1.12
commands:
# AWS Codebuild Go images use /go for the $GOPATH so copy the src code into that dir structure
- mkdir -p "/go/src/$(dirname ${PACKAGE})"
- ln -s "${CODEBUILD_SRC_DIR}" "/go/src/${PACKAGE}"
# Print all environment variables (handy for AWS CodeBuild logs)
- env
# Install Lambda Go
- go get github.com/aws/aws-lambda-go/lambda
pre_build:
commands:
# Make sure we're in the project directory within our GOPATH
- cd "/go/src/${PACKAGE}"
# Fetch all dependencies
- go get -t ./...
build:
commands:
# Build our Go app
- go build -o main
post_build:
commands:
- echo Build completed on `date`
artifacts:
type: zip
files:
- appspec.yml
- main
appspec.yml
version: 0.0
Resources:
- dlpQueueTimeFetcher:
Type: AWS::Lambda::Function
Properties:
Name: "dlpQueueTimeFetcher"
Alias: "v0"
CurrentVersion: "1"
TargetVersion: "2"
During the deployment CodeDeploy throws the following error: Action execution failed - BundleType must be either YAML or JSON.
It seems like CodeDeploy cannot find my appspec.yml file despite it being defined in the artifacts sections of my buildspec. What am I doing wrong here?
The problem you are facing is well known when connecting CodePipeline with CodeDeploy for Lambda deployment as CodeDeploy is looking for a Yaml or Json appspec file whereas the artifact presented by CodePipeline is a zip file containing the appspec:
https://forums.aws.amazon.com/thread.jspa?messageID=864336
CodePipeline: CodeDeploy reports "BundleType must be either YAML or JSON"
For now, you can use CloudFormation as a Deployment tool for your Lambda function in your Pipeline. The basic idea to deploy a Lambda function will be as follows:
Create a a SAM template of your Lambda function
A basic SAM template looks like:
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Resources:
FunctionName:
Type: 'AWS::Serverless::Function'
Properties:
Handler: index.handler
Runtime: nodejs6.10
CodeUri: ./code
Add a directory "code" and keep the lambda code files in this directory
Run the command to package and upload:
$ aws cloudformation package --template-file template.yaml --output-template packaged.yaml --s3-bucket {your_S3_bucket}
Deploy the package:
$ aws cloudformation deploy --template-file packaged.yaml --stack-name stk1 --capabilities CAPABILITY_IAM
You can keep the Template Code (Step1-2) in CodeCommit/Github and do the Step4 in a CodeBuild Step. For Step5, I recommend to do it via a CloudFormation action in CodePipeline that is fed the "packaged.yaml" file as input artifact.
The above process is detailed here: https://docs.aws.amazon.com/en_us/lambda/latest/dg/build-pipeline.html