Is it possible/recommended to use `sam build` in AWS CodeBuild? - amazon-web-services

This question spun out of this one. Now that I have a better understanding of what was going wrong there, and a workable, if imperfect, solution, I'm submitting a more focused follow-up (I'm still something of a novice at StackOverflow - please let me know if this contravenes etiquette, and I should follow-up on the original).
This page suggests that "You use AWS CodeBuild to build, locally test, and package your serverless application". However, when I include a sam build command in my buildspec.yml, I get the following log output, suggesting that sam is not installed on CodeBuild images:
[Container] 2018/12/31 11:41:49 Running command sam build --use-container
sh: 1: sam: not found
[Container] 2018/12/31 11:41:49 Command did not exit successfully sam build --use-container exit status 127
[Container] 2018/12/31 11:41:49 Phase complete: BUILD Success: false
[Container] 2018/12/31 11:41:49 Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: sam build --use-container. Reason: exit status 127
Furthermore, if I install SAM with pip install aws-sam-cli, running sam build --use-container in CodeBuild gives an error. sam build itself succeeds, but since it doesn't install test dependencies, I'd still need to use pip install -r tests/requirements-test.txt -t . to be able to run tests in CodeBuild. Moreover, this suggests that --use-container is required for "packages that have natively compiled programs").
This makes me wonder whether I'm trying to do something wrong. What's the recommended way of building SAM services in a CI/CD workflow on AWS?

2019_10_18 - Update (confirming #Spiff answer above):
Apparently Codebuild now work seamlessly with SAM, that's all I needed in buildspec.yml for a lambda using pandas and psycopg2-binary:
version: 0.2
phases:
install:
runtime-versions:
python: 3.7
pre_build:
commands:
- python -m unittest discover tests
build:
commands:
- sam build
post_build:
commands:
- sam package --output-template-file packaged.yaml --s3-bucket my-code-pipeline-bucketz
artifacts:
type: zip
files:
- packaged.yaml
Cheers

Please see below for buildspec.yaml that works for me when using AWS SAM with AWS CodeBuild, with cloudformation.yml
phases:
build:
commands:
- pip install --user aws-sam-cli
- USER_BASE_PATH=$(python -m site --user-base)
- export PATH=$PATH:$USER_BASE_PATH/bin
- sam build -t cloudformation.yml
- aws cloudformation package --template-file .aws-sam/build/template.yaml --s3-bucket <TARGET_S3_BUCKET> --output-template-file cloudformation-packaged.yaml
- aws s3 cp ./cloudformation-packaged.yaml <TARGET_S3_BUCKET>/cloudformation-packaged.yaml
In the result I get a deployment package and packaged cloudformation template in the TARGET_S3_BUCKET.
For each function in the ./src folder, I have a requirements.txt file that includes all the dependencies, but I dont run pip install -r requirements.txt manually.

If you want to run sam build command in CodeBuild, you must install aws-sam-cli first (probably in the install phase of buildspec.yml file) i.e. by running pip install aws-sam-cli command or alike.
--use-container option in the sam build command will cause the command to pull in the Docker image resembling the AWS Lambda execution environment, then run the container from this Docker image to pip install (if your lambda is written in Python) your function dependencies for creating your lambda deployment package. This will ensure that the lambda function will use native compiled libraries that are compatible with the actual runtime environment of AWS Lambda.
Therefore, if you specify --use-container option for sam build command running in CodeBuild, you also need to make sure that a Docker image used by your CodeBuild build project must support Docker runtime.
The most easiest way is to use CodeBuild build environment named aws/codebuild/standard:2.0 Docker image. Enabling Docker runtime in runtime-versions property in the install phases of your buildspec.yml. Also you might need to enable PrevilegedMode of your CodeBuild project in order to connect with Docker daemon from your build environment.

As of October 2019 I had no issues whatsoever deploying a serverless application with codebuild using sam build,
First of all --user is not needed for pip install aws-sam-cli. In fact including --user appears to be the only reason that sam is not in the path.
In addition the --use-container is not needed either as long as no native libraries are built, like psycopg

Related

What is the difference between npm package cdk and aws-cdk?

In shell scripts interacting with the AWS CDK Toolkit, I often see both npx -- cdk and npx -- aws-cdk. Apparently, there are two npm packages cdk and aws-cdk. What is the difference between these two?
Moreover, I make confusing observations when interacting with these two packages.
npx -- cdk --version # output: Need to install the following packages: cdk#2.32.1
npx -- aws-cdk --version # output: Need to install the following packages: aws-cdk#2.32.1
Since neither package is installed locally, both commands want to install the corresponding package, fine.
npm install -g cdk#2.32.0
npx -- cdk --version # output: 2.32.0 (build 00e0c2d)
npx -- aws-cdk --version # output: Need to install the following packages: aws-cdk#2.32.1
After installing cdk locally, npx -- cdk executes the local package and npx -- aws-cdk still wants to install aws-cdk, also fine.
npm install -g aws-cdk#2.32.0
npx -- cdk --version # output: 2.32.0 (build 00e0c2d)
npx -- aws-cdk --version # output: Need to install the following packages: aws-cdk#2.32.1
Now it gets confusing. Although we locally installed aws-cdk rather than cdk, npx -- cdk still executes some local package and npx -- aws-cdk still wants to install aws-cdk. Why?
The aws-cdk package provides a cdk command, the entry point to the AWS Cloud Development Kit.
When you install aws-cdk package in node_modules/.bin there is a cdk command installed.
This allows one to use cdk command inside package.json scripts.
The cdk command is also the only command added to .bin by aws-cdk.
As per all of the documentation, in order to interact with AWS CDK from CLI, one should use cdk command. That is even though the package name is aws-cdk.
Now, the npx provides a convenient way to run a command from a package regardless if it is already installed.
This works in the most straightforward way when the package name matches the command name it provides. This is not the case for aws-cdk.
In order to fix that, and allow anyone to run npx cdk ... the AWS CDK decided to publish a package named cdk. This is a small wrapper around cdk command provided by aws-cdk package.

How to use git lfs in AWS CodeBuild?

Since AWS CodeBuild doesn't seem to support git LFS (Large File System) I tried to install it:
version: 0.2
phases:
install:
commands:
- apt-get install -y bash curl
- curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash
- apt-get install -y git-lfs
pre_build:
commands:
- echo Downloading LFS files
- git lfs pull
build:
commands:
- echo Build started on `date`
post_build:
commands:
- echo Build completed on `date`
For the above code I'm getting the following error (renamed repo address):
[Container] 2020/06/18 16:02:17 Running command git lfs pull
fatal: could not read Password for 'https://username#bitbucket.org': No such device or address
batch response: Git credentials for https://username#bitbucket.org/company/repo.git not found.
error: failed to fetch some objects from 'https://username#bitbucket.org/company/repo.git/info/lfs'
[Container] 2020/06/18 16:02:17 Command did not exit successfully git lfs pull exit status 2
[Container] 2020/06/18 16:02:17 Phase complete: PRE_BUILD State: FAILED
[Container] 2020/06/18 16:02:17 Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: git lfs pull. Reason: exit status 2
Can I do something else in order to fetch LFS files?
CodeBuild does not natively support git LFS. The workaround would be to set up Git LFS 1 and cloning the repository 2 as part of the buildspec.yml execution.
Use 'git-credential-helper: yes' in buildspec for CodeBuild to provide the credentials to git commands 3.
CodeBuild does not support Git LFS, however it's possible to install it on-the-fly and then run git lfs pull from the source directory to download the files. Like this:
env:
git-credential-helper: yes
phases:
install:
commands:
- cd /tmp/
- curl -OJL https://github.com/git-lfs/git-lfs/releases/download/v2.13.2/git-lfs-linux-amd64-v2.13.2.tar.gz
- tar xzf git-lfs-linux-amd64-v2.13.2.tar.gz
- ./install.sh
- cd $CODEBUILD_SRC_DIR
pre_build:
commands:
- git lfs pull
<rest of your buildspec.yml file>
CodeBuild doesn't support Git LFS out of the box. One workaround would be to install it manually, but this will work only if you are connecting to GitHub, BitBucket or other provider directly (e.g. via SSH key).
If you are using it with CodePipeline and using repositories connection (aka "CodeStar Source Connections") then it won't work. When you connect your BitBucket or GitHub account this way, it creates some kind of "proxy" that doesn't support git-lfs resources:
batch response: Repository or object not found: https://codestar-connections.eu-central-1.amazonaws.com/git-http/[..].git/info/lfs/objects/batch
Check that it exists and that you have proper access to it
Failed to fetch some objects from 'https://codestar-connections.eu-central-1.amazonaws.com/git-http/[..].git/info/lfs'
With GitHub however there is a workaround:
GitHub CodeBuild git-lfs workaround
First you have to make sure that in the pipeline's Source stage, source output artifact it set to CODE_ZIP which equals to following setting in the Console:
Then in GitHub, in the repository settings, make sure that git-lfs resouces are included in source code archives:
This will make it work. Now source code downloaded by CodePipeline and passed to CodeBuild will include git-lfs files.

What is the best way to do CI/CD with AWS CDK (python) using GitLab CI?

I am using AWS CDK (with Python) for a containerized application that runs on Fargate. I would like to run cdk deploy in a GitLab CI process and pass the git tag as an environment variable that replaces the container running in Fargate. I am currently doing something similar with CloudFormation (aws cloudformation update-stack ...). Is anyone else doing CI/CD with AWS CDK in this way? Is there a better way to do it?
Also, what should I use for my base image for this job? I was thinking that I can either start with a python container and install node or vice versa. Or maybe there is prebuilt container somewhere that I haven't been able to find yet.
Here is start that seems to be working well:
CDK:
image: python:3.8
stage: deploy
before_script:
- apt-get -qq update && apt-get -y install nodejs npm
- node -v
- npm i -g aws-cdk
- cd awscdk
- pip3 install -r requirements.txt
script:
- cdk diff
- cdk deploy --require-approval never
Edit 2020-05-04:
CDK can build docker images during cdk deploy, but it needs access to docker. If you don't need docker, the above CI job definition should be fine. Here's the current CI job I'm using:
cdk deploy:
image: docker:19.03.1
services:
- docker:19.03.5-dind
stage: deploy
only:
- master
before_script:
- apk add --no-cache python3
- python3 -V
- pip3 -V
- apk add nodejs-current npm
- node -v
- npm i -g aws-cdk
- cd awscdk
- pip3 install -r requirements.txt
script:
- cdk bootstrap aws://$AWS_ACCOUNT_ID/$AWS_DEFAULT_REGION
- cdk deploy --require-approval never
The cdk bootstrap is needed because I am using assets in my cdk code:
self.backend_task.add_container(
"DjangoBackend",
image=ecs.AssetImage(
"../backend",
file="scripts/prod/Dockerfile",
target="production",
),
logging=ecs.LogDrivers.aws_logs(stream_prefix="Backend"),
environment=environment_variables,
command=["/start_prod.sh"],
)
Here's more information on cdk bootstrap: https://github.com/aws/aws-cdk/blob/master/design/cdk-bootstrap.md
you definitely have to use CDK deploy inside the CI/CD pipeline if you have lambda or ECS assets, otherwise, you could run CDK synth and pass the resulting Cloudformation to AWS Code Deploy. That means a lot of your CI/CD will be spent deploying which might drain your free tier build minutes or just means you pay more (AWS Code Deploy is free)
I do something similar with Golang in CircleCi. I use the Go base image and install nodejs and cdk. I use this base image to build all my go binaries, the vuejs frontend and compile cdk typescript and deploy it.
FROM golang:1.13
RUN go get -u -d github.com/magefile/mage
WORKDIR $GOPATH/src/github.com/magefile/mage
RUN go run bootstrap.go
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
RUN apt-get install -y nodejs
RUN npm i -g aws-cdk#1.36.x
RUN npm i -g typescript
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt update && apt install yarn
I hope that helps.
Also, what should I use for my base image for this job? I was thinking that I can either start with a python container and install node or vice versa. Or maybe there is prebuilt container somewhere that I haven't been able to find yet.
For anyone looking for how to implement CI/CD with AWS CDK Python in 2022, here's a tested solution:
Use python:3.10.8 as the base image in your CI/CD
(or any image with Debian 11)
Install Node.js 16 from NodeSource: curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
Install aws-cdk: npm i -g aws-cdk
You can add the two latter steps as inline scripts in your CI/CD pipeline so you do not need to build your own Docker image.
Here's a full example for Bitbucket Pipelines:
image: python:3.10.8
run-tests: &run-tests
step:
name: Run tests
script:
# Node 16
- curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
- npm i -g aws-cdk
- pip install -r requirements-dev.txt
- pytest
pipelines:
pull-requests:
"**":
- <<: *run-tests
branches:
master:
- <<: *run-tests
Note that the above instructions do not install Docker engine. In Bitbucket Pipelines, Docker can be used simply by adding
services:
- docker
in the configuration file.
If cdk deploy is giving you the error:
/usr/lib/node_modules/aws-cdk/lib/index.js:12422
home = path.join((os.userInfo().homedir ?? os.homedir()).trim(), ".cdk");
then the node version is out of date. This can be fixed by updating the docker image which also requires pip3:
cdk deploy:
image: docker:20.10.21
services:
- docker:20.10.21-dind
stage: deploy
only:
- master
before_script:
- apk add --no-cache python3
- python3 -V
- apk add py3-pip
- pip3 -V

AWS Lambda Function can import a module when run locally, but not when deployed

I am attempting to expand on this guide, by building a CodePipeline to pick up changes in GitHub, build them, and deploy the changes to my Lambda. sam build --use-container; sam local start-api allows me to successfully call the function locally - but when I deploy the function to AWS, the code fails to import a dependency.
My code depends on requests. I have duly included that in my requirements.txt file:
requests==2.20.0
My buildspec.yml includes directions to install the dependencies
version: 0.1
phases:
install:
commands:
- pip install -r hello_world/requirements.txt -t .
- pip install -U pytest
pre_build:
commands:
- python -m pytest tests/
build:
commands:
- aws cloudformation package --template-file template.yaml --s3-bucket <my_bucket>
--output-template-file outputTemplate.yml
artifacts:
type: zip
files:
- '**/*'
When my package builds in CodeBuild, that is acknowledged:
[Container] 2018/12/27 23:16:44 Waiting for agent ping
[Container] 2018/12/27 23:16:46 Waiting for DOWNLOAD_SOURCE
[Container] 2018/12/27 23:16:46 Phase is DOWNLOAD_SOURCE
[Container] 2018/12/27 23:16:46 CODEBUILD_SRC_DIR=/codebuild/output/src775882062/src
[Container] 2018/12/27 23:16:46 YAML location is /codebuild/output/src775882062/src/buildspec.yml
[Container] 2018/12/27 23:16:46 Processing environment variables
[Container] 2018/12/27 23:16:46 Moving to directory /codebuild/output/src775882062/src
[Container] 2018/12/27 23:16:46 Registering with agent
[Container] 2018/12/27 23:16:46 Phases found in YAML: 3
[Container] 2018/12/27 23:16:46 PRE_BUILD: 1 commands
[Container] 2018/12/27 23:16:46 BUILD: 1 commands
[Container] 2018/12/27 23:16:46 INSTALL: 2 commands
[Container] 2018/12/27 23:16:46 Phase complete: DOWNLOAD_SOURCE Success: true
[Container] 2018/12/27 23:16:46 Phase context status code: Message:
[Container] 2018/12/27 23:16:46 Entering phase INSTALL
[Container] 2018/12/27 23:16:46 Running command pip install -r hello_world/requirements.txt -t .
Collecting requests==2.20.0 (from -r hello_world/requirements.txt (line 1))
Downloading https://files.pythonhosted.org/packages/f1/ca/10332a30cb25b627192b4ea272c351bce3ca1091e541245cccbace6051d8/requests-2.20.0-py2.py3-none-any.whl (60kB)
...
But when I call the deployed function, I get an error:
Unable to import module 'app': No module named 'requests'
This seems very similar to this question, but I'm not using PYTHONPATH in my Lambda building.
EDIT: I added some debugging code to files in this package, to try to get a sense of their runtime environment. I also added similar debugging to another package that I deploy to Lambda via CodePipeline (though this one doesn't use SAM). Debugging code is below:
import os, sys
print('Inside ' + __file__)
for path in sys.path:
print(path)
if (os.path.exists(path)):
print(os.listdir(path))
for f in os.listdir(path):
if f.startswith('requests'):
print('Found requests!')
print()
This code attempts to determine if the requests module is present in the sys.path of the Lambda's runtime environment - and, if so, where.
For this (SAM-enabled) package, requests was not found anywhere. In the non-SAM-enabled package, requests (as well as all the other requirements.txt-declared dependencies of the package) was found in /var/task.
It looks like either CodeBuild isn't bundling the dependencies of the function alongside the source, or CloudFormation isn't deploying those dependencies. I suspect that this is something to do with the fact that this is a SAM-defined function, and not a "vanilla" Cloudformation one.
This page says that "You can also use other AWS services that integrate with AWS SAM to automate your deployments", but I can't see how to get CodePipeline to run sam deploy instead of aws cloudformation deploy (although this page claims that they are synonyms).
EDIT2 - I believe I've found the problem. For context, recall that I have two packages that are deploying Lambdas via CodePipeline (or attempting to) - the one referred to in this question, which refers to the Lambda as AWS::Serverless::Function, and a second, which uses AWS::Lambda::Function. The first Function's code was defined as a relative location (i.e., a reference to a directory in my package: CodeUri: main/), whereas the second Function's Code was a reference to an S3 location (fetched, in CodePipeline, with Fn::GetArtifactAtt": ["built", "ObjectKey"]} or ...BucketName"]})
The following is a sample of the first package's CodeBuild output:
[Container] 2018/12/30 19:19:48 Running command aws cloudformation package --template-file template.yaml --s3-bucket pop-culture-serverless-bucket --output-template-file outputTemplate.yml
Uploading to 669099ba3d2258eeb7391ad772bf870d 4222 / 4222.0 (100.00%)
Successfully packaged artifacts and wrote output template to file outputTemplate.yml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /codebuild/output/src110881899/src/outputTemplate.yml --stack-name <YOUR STACK NAME>
Compare with the same output from the second package's CodeBuild output:
....
[Container] 2018/12/30 16:42:27 Running command aws cloudformation package --template-file template.json --s3-bucket {BUCKET_NAME} --output-template-file outputTemplate.yml
Successfully packaged artifacts and wrote output template to file outputTemplate.yml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /codebuild/output/src282566886/src/outputTemplate.yml --stack-name <YOUR STACK NAME>
This suggests that the first package's aws cloudformation package call results in an upload of a file (669099ba3d2258eeb7391ad772bf870d) to S3 which is based only on the content of the template.yaml, whereas the "output" of the Build stage of the second package's CodePipeline is a zip of the directory that CodeBuild has been running in - which includes the dependencies (because of the calls to pip install).
I could get around this by simply changing the template.yaml of my SAM-template Function to reference an S3 location - but this would mean that I would be unable to test updates to the function locally (with, e.g., sam local start-api) without editing the template, since it would reference the S3 location and so wouldn't be affected by local changes.
Ideally, I want to find a way to include the dependencies of the code in the packaged-and-uploaded S3 file. From local testing, it appears that running sam package/aws cloudformation package without having first run sam build results in just the source code (no dependencies) being included. However, I can't run sam build in CodeBuild, since SAM isn't installed there.
(This also suggests that I have been unintentionally deploying the test-dependencies of my second package - since it was required to install them in CodeBuild (in order to run tests))
The reason that your lambda execution says "Unable to import module" when running in the actual AWS Lambda execution environment is because your lambda deployment package (that was uploaded to S3 by aws cloudformation package command) is missing required dependencies specified in your requirements.txt.
Command like aws cloudformation package or sam package will work with AWS::Serverless::Function resource in your CloudFormation template by zipping all contents (regardless of whether it is source code, dependencies or any other stuff) in a directory specified via CodeUri property then it will upload that resulting zip file to S3 bucket giving you a transformed CloudFormation template where the S3 bucket path to your deployment package replaces the path to the source code in your local machine specified in CodeUri property.
Looking at your buildspec.yml, I think the problem comes from -t . option you specified in pip install -r hello_world/requirements.txt -t . command in the install phase. This will install dependencies in a current directory (usually the root directory of your project) and not in the directory where the source code of hello_world lambda function resides. Thus, the dependencies will not get zipped together with the source code in the later aws cloudformation package step.
In general, when you create a lambda function deployment package (whether it's SAM-enabled or plain-old Lambda), you need to bundle everything (source code, dependencies, resources, etc.) that is used in your app. You usually do that by:-
Use sam build command if it's SAM-enabled CloudFormation template. This command will automatically find your requirements.txt and install specified dependencies into the .aws-sam directory in preparation for uploading to S3.
Manually run pip install -r requirements.txt to the right directory where contents get zipped as a deployment package for deploying lambda function. This works in both SAM-enabled or plain-old Lambda CloudFormation template.
I found a "solution" to this, by installing the dependencies of my code in the main directory rather than the root directory. However, I believe that a superior option would be to use layers to hold dependencies.
If your CodeUri is pointing to /main, the content of this folder will be zip and uploaded to S3 when running aws cloudformation package, but without the dependencies.
The difference when running sam package is that it installs the dependencies from requirements.txt for you and outputs it to .aws-sam/build/<functionname> folder.
So, in order to package the dependencies you need to access the function folder and install the dependencies locally, eg.
pip install -r requirements.txt -t .
then run aws cloudformation package --s3-bucket <YOUR_BUCKET> --template-file <YOUR TEMPLATE YAML> --output-template-file <OUTPUT TEMPLATE NAME YAML>.
CodeBuild build environments (specifically when using managed images) are based on Ubuntu base images - these dependencies might not be compatible when running on Lambda. This is because Lambda container environments are based on Amazon Linux - https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html
You can try fixing this by packaging the dependency in your source bundle and skipping this from your 'requirements.txt' file.
If I'm not wrong a similar issue is addressed at - Using moviepy, scipy and numpy in amazon lambda

AWS CodeBuild /codebuild/output/tmp/script.sh: docker: not found

I am using AWS CodeBuild to build my application. I am using example build spec file as given here: https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec-ref-example
I have already uploaded my custom Docker image to AWS ECR having requisites to build my application (Java/Scala based).
I get following error:
Reading package lists...
[Container] 2018/10/26 10:40:07 Running command echo Entered the install phase...
Entered the install phase...
[Container] 2018/10/26 10:40:07 Running command docker login -u AWS -p
.....
/codebuild/output/tmp/script.sh: 4: /codebuild/output/tmp/script.sh: docker: not found
Why should I get this error ? AWS CodeBuild is supposed to download this Docker image from ECR and then follow the instructions that I provide in the build spec file for building my application.
The example build.spec file assumes that your build image has Docker already installed. I was assuming "wrongly" that CodeBuild will install/configure Docker tools inside the image automatically.
Issue looks similar to AWS CodeBuild - docker: not found. I can't paste the same response to this question. So, please check my response there on how to enable Docker inside your build container to see if that solves your issue.