├── .dockerignore ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── check.yml ├── .gitignore ├── .mergify.yml ├── .versionrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── THIRD-PARTY ├── VERSION ├── assets └── demo.gif ├── buildspec.yml ├── buildspec_push_release.yml ├── buildspec_stage_release.yml ├── demo.sh ├── example ├── Dockerfile ├── deploy │ ├── serverless.yml │ ├── template.json │ └── template.yaml ├── function │ └── src │ │ ├── goodbye.php │ │ └── hello.php └── runtime │ └── bootstrap ├── go.mod ├── go.sum ├── img2lambda ├── cli │ └── main.go ├── clients │ └── clients.go ├── extract │ ├── repack_image.go │ └── repack_image_test.go ├── internal │ └── testing │ │ ├── generate_mocks.go │ │ └── mocks │ │ ├── image_mocks.go │ │ └── lambda_mocks.go ├── publish │ ├── publish_layers.go │ └── publish_layers_test.go ├── types │ └── types.go └── version │ └── version.go └── scripts ├── build_binary.sh ├── build_example.sh └── mockgen.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | bin 2 | output 3 | AUTHORS.md 4 | CONTRIBUTING.md 5 | LICENSE 6 | NOTICE 7 | README.md 8 | Dockerfile 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | on: 2 | [pull_request, push] 3 | 4 | name: Check 5 | 6 | jobs: 7 | dockerfile: 8 | name: Build Docker image from Dockerfile 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - run: | 13 | docker build -t img2lambda . 14 | 15 | docker-make-target: 16 | name: Build Docker image from Makefile target 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | - run: | 21 | make docker-build 22 | make docker-test 23 | 24 | build-binaries: 25 | name: Build binaries and run integration test 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v2 29 | - uses: actions/setup-go@v2 30 | with: 31 | go-version: '1.13' 32 | - run: | 33 | make install-tools 34 | make 35 | make integration-test 36 | make stage-release-binaries 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | bin/ 15 | output/ 16 | 17 | GITCOMMIT_SHA -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: Automatically merge on CI success and review approval 3 | conditions: 4 | - base=mainline 5 | - "#approved-reviews-by>=1" 6 | - approved-reviews-by=@awslabs/developer-experience 7 | - -approved-reviews-by~=author 8 | - status-success=Build Docker image from Dockerfile 9 | - status-success=Build Docker image from Makefile target 10 | - status-success=Build binaries and run integration test 11 | - status-success=Semantic Pull Request 12 | - label!=work-in-progress 13 | - -title~=(WIP|wip) 14 | - -merged 15 | - -closed 16 | - author!=dependabot[bot] 17 | - author!=dependabot-preview[bot] 18 | actions: 19 | merge: 20 | method: squash 21 | strict: smart 22 | strict_method: merge 23 | 24 | - name: Automatically approve and merge Dependabot PRs 25 | conditions: 26 | - base=mainline 27 | - author=dependabot[bot] 28 | - label=dependencies 29 | - status-success=Build Docker image from Dockerfile 30 | - status-success=Build Docker image from Makefile target 31 | - status-success=Build binaries and run integration test 32 | - status-success=Semantic Pull Request 33 | - label!=work-in-progress 34 | - -title~=(WIP|wip) 35 | - -label~=(blocked|do-not-merge) 36 | - -merged 37 | - -closed 38 | actions: 39 | review: 40 | type: APPROVE 41 | merge: 42 | method: squash 43 | strict: smart 44 | strict_method: merge 45 | -------------------------------------------------------------------------------- /.versionrc: -------------------------------------------------------------------------------- 1 | { 2 | "packageFiles": [ 3 | { 4 | "filename": "VERSION", 5 | "type": "plain-text" 6 | } 7 | ], 8 | "bumpFiles": [ 9 | { 10 | "filename": "VERSION", 11 | "type": "plain-text" 12 | } 13 | ], 14 | "tagPrefix": "" 15 | } 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [1.2.7](https://github.com/awslabs/aws-lambda-container-image-converter/compare/1.2.6...1.2.7) (2020-11-08) 6 | 7 | ### [1.2.6](https://github.com/awslabs/aws-lambda-container-image-converter/compare/1.2.5...1.2.6) (2020-10-08) 8 | 9 | ### [1.2.5](https://github.com/awslabs/aws-lambda-container-image-converter/compare/1.2.4...1.2.5) (2020-09-02) 10 | 11 | ### [1.2.4](https://github.com/awslabs/aws-lambda-container-image-converter/compare/1.2.3...1.2.4) (2020-07-17) 12 | 13 | 14 | ### Bug Fixes 15 | 16 | * reduce default perms for output directory ([3383ec2](https://github.com/awslabs/aws-lambda-container-image-converter/commit/3383ec231c1f92a058ca320c716cbc913bc03720)) 17 | 18 | ### [1.2.3](https://github.com/awslabs/aws-lambda-container-image-converter/compare/1.2.2...1.2.3) (2020-07-07) 19 | 20 | ### [1.2.2](https://github.com/awslabs/aws-lambda-container-image-converter/compare/1.2.1...1.2.2) (2020-07-07) 21 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/awslabs/aws-lambda-aws-lambda-container-image-converter/issues), or [recently closed](https://github.com/awslabs/aws-lambda-aws-lambda-container-image-converter/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *mainline* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/aws-lambda-aws-lambda-container-image-converter/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/awslabs/aws-lambda-aws-lambda-container-image-converter/blob/mainline/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | FROM golang:1.13 AS builder 5 | 6 | WORKDIR /go/src/github.com/awslabs/aws-lambda-container-image-converter 7 | 8 | COPY . ./ 9 | 10 | RUN make install-tools && make 11 | 12 | FROM busybox:glibc 13 | COPY --from=builder /go/src/github.com/awslabs/aws-lambda-container-image-converter/bin/local/img2lambda /bin/img2lambda 14 | CMD [ "/bin/img2lambda" ] 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | ROOT := $(shell pwd) 5 | 6 | all: build 7 | 8 | SOURCEDIR := ./img2lambda 9 | BINARY_NAME=img2lambda 10 | SOURCES := $(shell find $(SOURCEDIR) -name '*.go') 11 | LOCAL_BINARY := bin/local/img2lambda 12 | LINUX_BINARY := bin/linux-amd64/img2lambda 13 | DARWIN_BINARY := bin/darwin-amd64/img2lambda 14 | WINDOWS_BINARY := bin/windows-amd64/img2lambda.exe 15 | LOCAL_PATH := $(ROOT)/scripts:${PATH} 16 | VERSION := $(shell cat VERSION) 17 | GITFILES := $(shell find ".git/") 18 | 19 | .PHONY: build 20 | build: format $(LOCAL_BINARY) 21 | 22 | $(LOCAL_BINARY): $(SOURCES) GITCOMMIT_SHA 23 | ./scripts/build_binary.sh ./bin/local img2lambda $(VERSION) $(shell cat GITCOMMIT_SHA) 24 | @echo "Built img2lambda" 25 | 26 | .PHONY: integration-test 27 | integration-test: $(LOCAL_BINARY) 28 | ./scripts/build_example.sh 29 | 30 | .PHONY: generate 31 | generate: $(SOURCES) 32 | PATH=$(LOCAL_PATH) go generate -x $(shell go list ./img2lambda/... | grep -v '/vendor/') 33 | 34 | .PHONY: format 35 | format: $(SOURCES) 36 | PATH=$(LOCAL_PATH) go fmt -x $(shell go list ./img2lambda/... | grep -v '/vendor/') 37 | 38 | .PHONY: install-tools 39 | install-tools: 40 | go get golang.org/x/tools/cmd/cover 41 | go get github.com/golang/mock/mockgen 42 | go get golang.org/x/tools/cmd/goimports 43 | 44 | .PHONY: windows-build 45 | windows-build: $(WINDOWS_BINARY) 46 | 47 | .PHONY: docker-build 48 | docker-build: 49 | docker run -v $(shell pwd):/usr/src/app/src/github.com/awslabs/aws-lambda-container-image-converter \ 50 | --workdir=/usr/src/app/src/github.com/awslabs/aws-lambda-container-image-converter \ 51 | --env GOPATH=/usr/src/app \ 52 | golang:1.13 make $(LINUX_BINARY) 53 | docker run -v $(shell pwd):/usr/src/app/src/github.com/awslabs/aws-lambda-container-image-converter \ 54 | --workdir=/usr/src/app/src/github.com/awslabs/aws-lambda-container-image-converter \ 55 | --env GOPATH=/usr/src/app \ 56 | golang:1.13 make $(DARWIN_BINARY) 57 | docker run -v $(shell pwd):/usr/src/app/src/github.com/awslabs/aws-lambda-container-image-converter \ 58 | --workdir=/usr/src/app/src/github.com/awslabs/aws-lambda-container-image-converter \ 59 | --env GOPATH=/usr/src/app \ 60 | golang:1.13 make $(WINDOWS_BINARY) 61 | 62 | .PHONY: docker-test 63 | docker-test: 64 | docker run -v $(shell pwd):/usr/src/app/src/github.com/awslabs/aws-lambda-container-image-converter \ 65 | --workdir=/usr/src/app/src/github.com/awslabs/aws-lambda-container-image-converter \ 66 | --env GOPATH=/usr/src/app \ 67 | --env IMG_TOOL_RELEASE=$(IMG_TOOL_RELEASE) \ 68 | golang:1.13 make 69 | 70 | .PHONY: stage-release-binaries 71 | stage-release-binaries: $(LINUX_BINARY) $(DARWIN_BINARY) $(WINDOWS_BINARY) 72 | 73 | $(WINDOWS_BINARY): $(SOURCES) GITCOMMIT_SHA 74 | TARGET_GOOS=windows GOARCH=amd64 ./scripts/build_binary.sh ./bin/release-$(VERSION) windows-amd64-img2lambda.exe $(VERSION) $(shell cat GITCOMMIT_SHA) 75 | @echo "Built img2lambda.exe for windows" 76 | 77 | $(LINUX_BINARY): $(SOURCES) GITCOMMIT_SHA 78 | TARGET_GOOS=linux GOARCH=amd64 ./scripts/build_binary.sh ./bin/release-$(VERSION) linux-amd64-img2lambda $(VERSION) $(shell cat GITCOMMIT_SHA) 79 | @echo "Built img2lambda for linux" 80 | 81 | $(DARWIN_BINARY): $(SOURCES) GITCOMMIT_SHA 82 | TARGET_GOOS=darwin GOARCH=amd64 ./scripts/build_binary.sh ./bin/release-$(VERSION) darwin-amd64-img2lambda $(VERSION) $(shell cat GITCOMMIT_SHA) 83 | @echo "Built img2lambda for darwin" 84 | 85 | GITCOMMIT_SHA: $(GITFILES) 86 | git rev-parse --short=7 HEAD > GITCOMMIT_SHA 87 | 88 | .PHONY: clean 89 | clean: 90 | - rm -rf ./bin 91 | - rm -rf ./output 92 | - rm -rf ./example/output 93 | - rm -f GITCOMMIT_SHA 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Note: this project is now archived!** AWS Lambda now natively supports packaging your function code as a container image and providing the image directly to Lambda. 2 | https://aws.amazon.com/about-aws/whats-new/2020/12/aws-lambda-now-supports-container-images-as-a-packaging-format/ 3 | 4 | ## AWS Lambda Container Image Converter 5 | 6 | This container image converter tool (img2lambda) extracts an AWS Lambda function deployment package from a container image (such as a Docker image). 7 | It also extracts AWS Lambda layers from a container image, and publishes them as new layer versions to Lambda. 8 | 9 | ![img2lambda Demo](assets/demo.gif) 10 | 11 | To extract a Lambda function deployment package, the tool copies all files under '/var/task' in the container image into a deployment package zip file. 12 | 13 | To extract Lambda layers, the tool copies all files under '/opt' in the container image, repackaging the individual container image layers as individual Lambda layer zip files. 14 | The published layer ARNs are stored in a file 'output/layers.json', which can be used as input when creating Lambda functions. 15 | Each layer is named using a "namespace" prefix (like 'img2lambda' or 'my-docker-image') and the SHA256 digest of the container image layer, in order to provide a way of tracking the provenance of the Lambda layer back to the container image that created it. 16 | If a layer is already published to Lambda (same layer name, SHA256 digest, and size), it will not be published again. 17 | Instead the existing layer version ARN will be written to the output file. 18 | 19 | **Table of Contents** 20 | 21 | 22 | 23 | - [Usage](#usage) 24 | - [Install](#install) 25 | + [Binaries](#binaries) 26 | + [From Source](#from-source) 27 | - [Permissions](#permissions) 28 | - [Examples](#examples) 29 | + [Docker Example](#docker-example) 30 | + [OCI Example](#oci-example) 31 | + [Deploy Manually](#deploy-manually) 32 | + [Deploy with AWS Serverless Application Model (SAM)](#deploy-with-aws-serverless-application-model-sam) 33 | + [Deploy with Serverless Framework](#deploy-with-serverless-framework) 34 | - [License Summary](#license-summary) 35 | - [Security Disclosures](#security-disclosures) 36 | 37 | 38 | 39 | ## Usage 40 | 41 | ``` 42 | USAGE: 43 | img2lambda [options] 44 | 45 | GLOBAL OPTIONS: 46 | --image value, -i value Name or path of the source container image. For example, 'my-docker-image:latest' or './my-oci-image-archive'. The image must be pulled locally already. 47 | --image-type value, -t value Type of the source container image. Valid values: 'docker' (Docker image from the local Docker daemon), 'oci' (OCI image archive at the given path and optional tag) (default: "docker") 48 | --region value, -r value AWS region (default: "us-east-1") 49 | --profile value, -p value AWS credentials profile. Credentials will default to the same chain as the AWS CLI: environment variables, default profile, container credentials, EC2 instance credentials 50 | --output-directory value, -o value Destination directory for output: function deployment package (function.zip) and list of published layers (layers.json, layers.yaml) (default: "./output") 51 | --layer-namespace value, -n value Prefix for the layers published to Lambda (default: "img2lambda") 52 | --dry-run, -d Conduct a dry-run: Repackage the image, but only write the Lambda layers to local disk (do not publish to Lambda) 53 | --description value, --desc value The description of this layer version (default: "created by img2lambda from image ") 54 | --license-info value, -l value The layer's software license. It can be an SPDX license identifier, the URL of the license hosted on the internet, or the full text of the license (default: no license) 55 | --compatible-runtime value, --cr value An AWS Lambda function runtime compatible with the image layers. To specify multiple runtimes, repeat the option: --cr provided --cr python2.7 (default: "provided") 56 | --help, -h show help 57 | --version, -v print the version 58 | ``` 59 | 60 | Note: To avoid any sensitive information being packaged into a Lambda function deployment package or Lambda layer, do not store any sensitive information like credentials in the source container image's /opt or /var/task directories. 61 | 62 | ## Install 63 | 64 | #### Binaries 65 | 66 | Download pre-built binaries from the [Releases Page](https://github.com/awslabs/aws-lambda-container-image-converter/releases). 67 | 68 | #### From Source 69 | 70 | With go 1.11+: 71 | 72 | ``` 73 | $ git clone https://github.com/awslabs/aws-lambda-container-image-converter 74 | $ cd aws-lambda-container-image-converter 75 | $ make 76 | $ ./bin/local/img2lambda --help 77 | ``` 78 | 79 | ## Permissions 80 | 81 | No credentials are required for dry-runs of the img2lambda tool. When publishing layers to Lambda, img2lambda will look for credentials in the following order (using the default provider chain in the AWS SDK for Go). 82 | 83 | 1. Environment variables. 84 | 1. Shared credentials file. 85 | 1. If running on Amazon ECS (with task role) or AWS CodeBuild, IAM role from the container credentials endpoint. 86 | 1. If running on an Amazon EC2 instance, IAM role for Amazon EC2. 87 | 88 | The credentials must have the following permissions: 89 | ``` 90 | { 91 | "Version": "2012-10-17", 92 | "Statement": [ 93 | { 94 | "Sid": "MinimalPermissions", 95 | "Effect": "Allow", 96 | "Action": [ 97 | "lambda:GetLayerVersion", 98 | "lambda:ListLayerVersions", 99 | "lambda:PublishLayerVersion" 100 | ], 101 | "Resource": [ 102 | "arn:aws:lambda:::layer:-sha256-*", 103 | "arn:aws:lambda:::layer:-sha256-*:*" 104 | ] 105 | } 106 | ] 107 | } 108 | ``` 109 | 110 | For example: 111 | ``` 112 | { 113 | "Version": "2012-10-17", 114 | "Statement": [ 115 | { 116 | "Sid": "MinimalPermissions", 117 | "Effect": "Allow", 118 | "Action": [ 119 | "lambda:GetLayerVersion", 120 | "lambda:ListLayerVersions", 121 | "lambda:PublishLayerVersion" 122 | ], 123 | "Resource": [ 124 | "arn:aws:lambda:us-east-1:123456789012:layer:img2lambda-sha256-*", 125 | "arn:aws:lambda:us-east-1:123456789012:layer:img2lambda-sha256-*:*" 126 | ] 127 | } 128 | ] 129 | } 130 | ``` 131 | 132 | ## Examples 133 | 134 | ### Docker Example 135 | 136 | Build the example Docker image to create a PHP Lambda custom runtime and Hello World PHP function: 137 | ``` 138 | cd example 139 | 140 | docker build -t lambda-php . 141 | ``` 142 | 143 | The Hello World function can be invoked locally by running the Docker image: 144 | ``` 145 | docker run lambda-php hello '{"name": "World"}' 146 | 147 | docker run lambda-php goodbye '{"name": "World"}' 148 | ``` 149 | 150 | Run the tool to both create a Lambda deployment package that contains the Hello World PHP function, and to create and publish Lambda layers that contain the PHP custom runtime: 151 | ``` 152 | ../bin/local/img2lambda -i lambda-php:latest -r us-east-1 -o ./output 153 | ``` 154 | 155 | ### OCI Example 156 | 157 | Create an OCI image from the example Dockerfile: 158 | ``` 159 | cd example 160 | 161 | podman build --format oci -t lambda-php . 162 | 163 | podman push lambda-php oci-archive:./lambda-php-oci 164 | ``` 165 | 166 | Run the tool to both create a Lambda deployment package that contains the Hello World PHP function, and to create and publish Lambda layers that contain the PHP custom runtime: 167 | ``` 168 | ../bin/local/img2lambda -i ./lambda-php-oci -t oci -r us-east-1 -o ./output 169 | ``` 170 | 171 | ### Deploy Manually 172 | Create a PHP function that uses the layers and deployment package extracted from the container image: 173 | ``` 174 | aws lambda create-function \ 175 | --function-name php-example-hello \ 176 | --handler hello \ 177 | --zip-file fileb://./output/function.zip \ 178 | --runtime provided \ 179 | --role "arn:aws:iam::XXXXXXXXXXXX:role/service-role/LambdaPhpExample" \ 180 | --region us-east-1 \ 181 | --layers file://./output/layers.json 182 | ``` 183 | 184 | Finally, invoke the function: 185 | ``` 186 | aws lambda invoke \ 187 | --function-name php-example-hello \ 188 | --region us-east-1 \ 189 | --log-type Tail \ 190 | --query 'LogResult' \ 191 | --output text \ 192 | --payload '{"name": "World"}' hello-output.txt | base64 --decode 193 | 194 | cat hello-output.txt 195 | ``` 196 | 197 | ### Deploy with AWS Serverless Application Model (SAM) 198 | 199 | See [the sample template.yaml](example/deploy/template.yaml) and [the sample template.json](example/deploy/template.json). 200 | 201 | Insert the layers ARNs into the function definition: 202 | ``` 203 | cd example/deploy 204 | 205 | sed -i 's/^- / - /' ../output/layers.yaml && \ 206 | sed -e "/LAYERS_PLACEHOLDER/r ../output/layers.yaml" -e "s///" template.yaml > template-with-layers.yaml 207 | 208 | OR 209 | 210 | cd example/deploy 211 | 212 | sed -e "/\"LAYERS_PLACEHOLDER\"/r ../output/layers.json" -e "s///" template.json | jq . > template-with-layers.json 213 | ``` 214 | 215 | Deploy the function: 216 | ``` 217 | sam package --template-file template-with-layers.yaml \ 218 | --output-template-file packaged.yaml \ 219 | --region us-east-1 \ 220 | --s3-bucket 221 | 222 | sam deploy --template-file packaged.yaml \ 223 | --capabilities CAPABILITY_IAM \ 224 | --region us-east-1 \ 225 | --stack-name img2lambda-php-example 226 | 227 | OR 228 | 229 | sam package --template-file template-with-layers.json \ 230 | --output-template-file packaged.json \ 231 | --region us-east-1 \ 232 | --s3-bucket 233 | 234 | sam deploy --template-file packaged.json \ 235 | --capabilities CAPABILITY_IAM \ 236 | --region us-east-1 \ 237 | --stack-name img2lambda-php-example 238 | ``` 239 | 240 | Invoke the function: 241 | ``` 242 | aws lambda invoke \ 243 | --function-name sam-php-example-hello \ 244 | --region us-east-1 \ 245 | --log-type Tail \ 246 | --query 'LogResult' \ 247 | --output text \ 248 | --payload '{"name": "World"}' hello-output.txt | base64 --decode 249 | 250 | cat hello-output.txt 251 | ``` 252 | 253 | ### Deploy with Serverless Framework 254 | 255 | See [the sample serverless.yml](example/deploy/serverless.yml) for how to use the img2lambda-generated layers in your Serverless function. 256 | 257 | Deploy the function: 258 | ``` 259 | cd example/deploy 260 | 261 | serverless deploy -v 262 | ``` 263 | 264 | Invoke the function: 265 | ``` 266 | serverless invoke -f hello -l -d '{"name": "World"}' 267 | ``` 268 | 269 | ## License Summary 270 | 271 | This sample code is made available under a modified MIT license. See the LICENSE file. 272 | 273 | ## Security Disclosures 274 | 275 | If you would like to report a potential security issue in this project, please do not create a GitHub issue. Instead, please follow the instructions [here](https://aws.amazon.com/security/vulnerability-reporting/) or [email AWS security directly](mailto:aws-security@amazon.com). 276 | -------------------------------------------------------------------------------- /THIRD-PARTY: -------------------------------------------------------------------------------- 1 | ** github.com/aws/aws-sdk-go; version 1.16.16 -- https://github.com/aws/aws-sdk-go 2 | ** github.com/containers/image; version 1.3.0 -- https://github.com/containers/image 3 | ** github.com/docker/distribution; version 83389a148052d74ac602f5f1d62f86ff2f3c4aa5 -- https://www.docker.com 4 | ** github.com/docker/distribution; version 83389a148052d74ac602f5f1d62f86ff2f3c4aa5 -- https://www.docker.com 5 | ** github.com/docker/docker; version 6e3113f700dea1bf2785d94731b4b5a1e602d9ab -- https://github.com/moby/moby 6 | 7 | Apache License 8 | 9 | Version 2.0, January 2004 10 | 11 | http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND 12 | DISTRIBUTION 13 | 14 | 1. Definitions. 15 | 16 | 17 | 18 | "License" shall mean the terms and conditions for use, reproduction, and 19 | distribution as defined by Sections 1 through 9 of this 20 | document. 21 | 22 | 23 | 24 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 25 | owner that is granting the License. 26 | 27 | 28 | 29 | "Legal Entity" shall mean the union of the acting entity and all other entities 30 | that control, are controlled by, or are under common control with that entity. 31 | For the purposes of this definition, "control" means (i) the power, direct or 32 | indirect, to cause the direction or management of such entity, whether by 33 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 34 | outstanding shares, or (iii) beneficial ownership of such 35 | entity. 36 | 37 | 38 | 39 | "You" (or "Your") shall mean an individual or Legal Entity exercising 40 | permissions granted by this License. 41 | 42 | 43 | 44 | "Source" form shall mean the preferred form for making modifications, including 45 | but not limited to software source code, documentation source, and 46 | configuration files. 47 | 48 | 49 | 50 | "Object" form shall mean any form resulting from mechanical transformation or 51 | translation of a Source form, including but not limited to compiled object 52 | code, generated documentation, and conversions to other media 53 | types. 54 | 55 | 56 | 57 | "Work" shall mean the work of authorship, whether in Source or Object form, 58 | made available under the License, as indicated by a copyright notice that is 59 | included in or attached to the work (an example is provided in the Appendix 60 | below). 61 | 62 | 63 | 64 | "Derivative Works" shall mean any work, whether in Source or Object form, that 65 | is based on (or derived from) the Work and for which the editorial revisions, 66 | annotations, elaborations, or other modifications represent, as a whole, an 67 | original work of authorship. For the purposes of this License, Derivative Works 68 | shall not include works that remain separable from, or merely link (or bind by 69 | name) to the interfaces of, the Work and Derivative Works 70 | thereof. 71 | 72 | 73 | 74 | "Contribution" shall mean any work of authorship, including the original 75 | version of the Work and any modifications or additions to that Work or 76 | Derivative Works thereof, that is intentionally submitted to Licensor for 77 | inclusion in the Work by the copyright owner or by an individual or Legal 78 | Entity authorized to submit on behalf of the copyright owner. For the purposes 79 | of this definition, "submitted" means any form of electronic, verbal, or 80 | written communication sent to the Licensor or its representatives, including 81 | but not limited to communication on electronic mailing lists, source code 82 | control systems, and issue tracking systems that are managed by, or on behalf 83 | of, the Licensor for the purpose of discussing and improving the Work, but 84 | excluding communication that is conspicuously marked or otherwise designated in 85 | writing by the copyright owner as "Not a Contribution." 86 | 87 | 88 | 89 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 90 | of whom a Contribution has been received by Licensor and subsequently 91 | incorporated within the Work. 92 | 93 | 2. Grant of Copyright License. Subject to the terms and conditions of this 94 | License, each Contributor hereby grants to You a perpetual, worldwide, 95 | non-exclusive, no-charge, royalty-free, irrevocable copyright license to 96 | reproduce, prepare Derivative Works of, publicly display, publicly perform, 97 | sublicense, and distribute the Work and such Derivative Works in Source or 98 | Object form. 99 | 100 | 3. Grant of Patent License. Subject to the terms and conditions of this 101 | License, each Contributor hereby grants to You a perpetual, worldwide, 102 | non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this 103 | section) patent license to make, have made, use, offer to sell, sell, import, 104 | and otherwise transfer the Work, where such license applies only to those 105 | patent claims licensable by such Contributor that are necessarily infringed by 106 | their Contribution(s) alone or by combination of their Contribution(s) with the 107 | Work to which such Contribution(s) was submitted. If You institute patent 108 | litigation against any entity (including a cross-claim or counterclaim in a 109 | lawsuit) alleging that the Work or a Contribution incorporated within the Work 110 | constitutes direct or contributory patent infringement, then any patent 111 | licenses granted to You under this License for that Work shall terminate as of 112 | the date such litigation is filed. 113 | 114 | 4. Redistribution. You may reproduce and distribute copies of the Work or 115 | Derivative Works thereof in any medium, with or without modifications, and in 116 | Source or Object form, provided that You meet the following conditions: 117 | 118 | (a) You must give any other recipients of the Work or Derivative Works a copy 119 | of this License; and 120 | 121 | (b) You must cause any modified files to carry prominent notices stating that 122 | You changed the files; and 123 | 124 | (c) You must retain, in the Source form of any Derivative Works that You 125 | distribute, all copyright, patent, trademark, and attribution notices from the 126 | Source form of the Work, excluding those notices that do not pertain to any 127 | part of the Derivative Works; and 128 | 129 | (d) If the Work includes a "NOTICE" text file as part of its distribution, then 130 | any Derivative Works that You distribute must include a readable copy of the 131 | attribution notices contained within such NOTICE file, excluding those notices 132 | that do not pertain to any part of the Derivative Works, in at least one of the 133 | following places: within a NOTICE text file distributed as part of the 134 | Derivative Works; within the Source form or documentation, if provided along 135 | with the Derivative Works; or, within a display generated by the Derivative 136 | Works, if and wherever such third-party notices normally appear. The contents 137 | of the NOTICE file are for informational purposes only and do not modify the 138 | License. You may add Your own attribution notices within Derivative Works that 139 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 140 | provided that such additional attribution notices cannot be construed as 141 | modifying the License. 142 | 143 | You may add Your own copyright statement to Your modifications and may provide 144 | additional or different license terms and conditions for use, reproduction, or 145 | distribution of Your modifications, or for any such Derivative Works as a 146 | whole, provided Your use, reproduction, and distribution of the Work otherwise 147 | complies with the conditions stated in this License. 148 | 149 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 150 | Contribution intentionally submitted for inclusion in the Work by You to the 151 | Licensor shall be under the terms and conditions of this License, without any 152 | additional terms or conditions. Notwithstanding the above, nothing herein shall 153 | supersede or modify the terms of any separate license agreement you may have 154 | executed with Licensor regarding such Contributions. 155 | 156 | 6. Trademarks. This License does not grant permission to use the trade names, 157 | trademarks, service marks, or product names of the Licensor, except as required 158 | for reasonable and customary use in describing the origin of the Work and 159 | reproducing the content of the NOTICE file. 160 | 161 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in 162 | writing, Licensor provides the Work (and each Contributor provides its 163 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 164 | KIND, either express or implied, including, without limitation, any warranties 165 | or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 166 | PARTICULAR PURPOSE. You are solely responsible for determining the 167 | appropriateness of using or redistributing the Work and assume any risks 168 | associated with Your exercise of permissions under this License. 169 | 170 | 8. Limitation of Liability. In no event and under no legal theory, whether in 171 | tort (including negligence), contract, or otherwise, unless required by 172 | applicable law (such as deliberate and grossly negligent acts) or agreed to in 173 | writing, shall any Contributor be liable to You for damages, including any 174 | direct, indirect, special, incidental, or consequential damages of any 175 | character arising as a result of this License or out of the use or inability to 176 | use the Work (including but not limited to damages for loss of goodwill, work 177 | stoppage, computer failure or malfunction, or any and all other commercial 178 | damages or losses), even if such Contributor has been advised of the 179 | possibility of such damages. 180 | 181 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or 182 | Derivative Works thereof, You may choose to offer, and charge a fee for, 183 | acceptance of support, warranty, indemnity, or other liability obligations 184 | and/or rights consistent with this License. However, in accepting such 185 | obligations, You may act only on Your own behalf and on Your sole 186 | responsibility, not on behalf of any other Contributor, and only if You agree 187 | to indemnify, defend, and hold each Contributor harmless for any liability 188 | incurred by, or claims asserted against, such Contributor by reason of your 189 | accepting any such warranty or additional liability. END OF TERMS AND 190 | CONDITIONS 191 | 192 | APPENDIX: How to apply the Apache License to your work. 193 | 194 | To apply the Apache License to your work, attach the following boilerplate 195 | notice, with the fields enclosed by brackets "[]" replaced with your own 196 | identifying information. (Don't include the brackets!) The text should be 197 | enclosed in the appropriate comment syntax for the file format. We also 198 | recommend that a file or class name and description of purpose be included on 199 | the same "printed page" as the copyright notice for easier identification 200 | within third-party archives. 201 | 202 | Copyright [yyyy] [name of copyright owner] 203 | 204 | Licensed under the Apache License, Version 2.0 (the "License"); 205 | 206 | you may not use this file except in compliance with the License. 207 | 208 | You may obtain a copy of the License at 209 | 210 | http://www.apache.org/licenses/LICENSE-2.0 211 | 212 | Unless required by applicable law or agreed to in writing, software 213 | 214 | distributed under the License is distributed on an "AS IS" BASIS, 215 | 216 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 217 | 218 | See the License for the specific language governing permissions and 219 | 220 | limitations under the License. 221 | 222 | * For github.com/aws/aws-sdk-go see also this required NOTICE: 223 | AWS SDK for Go 224 | Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 225 | Copyright 2014-2015 Stripe, Inc. 226 | * For github.com/docker/distribution see also this required NOTICE: 227 | Copyright 2013-2017 Docker, Inc. 228 | * For github.com/docker/distribution see also this required NOTICE: 229 | Copyright 2013-2017 Docker, Inc. 230 | * For github.com/docker/docker see also this required NOTICE: 231 | Docker 232 | Copyright 2012-2017 Docker, Inc. 233 | 234 | This product includes software developed at Docker, Inc. 235 | (https://www.docker.com). 236 | 237 | This product contains software (https://github.com/kr/pty) developed 238 | by Keith Rarick, licensed under the MIT License. 239 | 240 | The following is courtesy of our legal counsel: 241 | 242 | 243 | Use and transfer of Docker may be subject to certain restrictions by the 244 | United States and other governments. 245 | It is your responsibility to ensure that your use and/or transfer does not 246 | violate applicable laws. 247 | 248 | For more information, please see https://www.bis.doc.gov 249 | 250 | See also https://www.apache.org/dev/crypto.html and/or seek legal counsel. 251 | 252 | ----- 253 | 254 | ** github.com/pkg/errors; version 0.8.1 -- https://github.com/pkg/errors 255 | Copyright (c) 2015, Dave Cheney 256 | 257 | Redistribution and use in source and binary forms, with or without 258 | modification, are permitted provided that the following conditions are met: 259 | 260 | 1. Redistributions of source code must retain the above copyright notice, this 261 | list of conditions and the following disclaimer. 262 | 263 | 2. Redistributions in binary form must reproduce the above copyright notice, 264 | this list of conditions and the following disclaimer in the documentation 265 | and/or other materials provided with the distribution. 266 | 267 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 268 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 269 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 270 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 271 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 272 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 273 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 274 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 275 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 276 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 277 | 278 | ----- 279 | 280 | ** github.com/mattn/go-zglob; version 0.0.1 -- https://github.com/mattn/go-zglob 281 | Copyright (c) 2017 Yasuhiro Matsumoto 282 | ** github.com/mholt/archiver; version 3.1.1 -- https://github.com/mholt/archiver 283 | Copyright (c) 2016 Matthew Holt 284 | ** github.com/urfave/cli; version 1.20.0 -- https://github.com/urfave/cli 285 | Copyright (c) 2016 Jeremy Saenz & Contributors 286 | 287 | MIT License 288 | 289 | Copyright (c) 290 | 291 | Permission is hereby granted, free of charge, to any person obtaining a copy of 292 | this software and associated documentation files (the "Software"), to deal in 293 | the Software without restriction, including without limitation the rights to 294 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 295 | of the Software, and to permit persons to whom the Software is furnished to do 296 | so, subject to the following conditions: 297 | 298 | The above copyright notice and this permission notice shall be included in all 299 | copies or substantial portions of the Software. 300 | 301 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 302 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 303 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 304 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 305 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 306 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 307 | SOFTWARE. -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.2.7 -------------------------------------------------------------------------------- /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-lambda-container-image-converter/47bd9b362740bfe0838476d6be9d571057b1cc99/assets/demo.gif -------------------------------------------------------------------------------- /buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | install: 5 | runtime-versions: 6 | golang: 1.13 7 | 8 | build: 9 | commands: 10 | # Run tests 11 | - make install-tools 12 | - make 13 | - make integration-test 14 | -------------------------------------------------------------------------------- /buildspec_push_release.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | install: 5 | runtime-versions: 6 | ruby: 2.6 7 | commands: 8 | # TODO: Upgrade to final v2 dpl production version 9 | # https://github.com/travis-ci/dpl/tree/v2.0.0.alpha.2#github-releases 10 | - "gem install dpl:'<2.0.0'" 11 | - git config credential.helper "store --file=.git/credentials" 12 | - echo "https://${GITHUB_TOKEN}:@github.com" > .git/credentials 13 | 14 | build: 15 | commands: 16 | # Push changelog update and new version tag 17 | - export NEW_VERSION=`cat VERSION` 18 | - export GIT_COMMIT_ID=`git rev-parse HEAD` 19 | - git push origin HEAD:mainline 20 | - git push origin $NEW_VERSION 21 | 22 | # Determine the artifacts to publish 23 | - "echo Artifacts to publish to the release target:" 24 | - cat ./$GIT_COMMIT_ID.manifest 25 | - export DPL_FILE_FLAGS="" 26 | - |- 27 | for artifact in `cat ./$GIT_COMMIT_ID.manifest` 28 | do 29 | export DPL_FILE_FLAGS="$DPL_FILE_FLAGS --file=$artifact --file=$artifact.md5 --file=$artifact.sha256" 30 | done 31 | - echo $DPL_FILE_FLAGS 32 | 33 | # Create a GitHub release for the new version 34 | - dpl --provider=releases --skip-cleanup --api_key=$GITHUB_TOKEN --target_commitish=$GIT_COMMIT_ID --name="img2lambda $NEW_VERSION" --repo=awslabs/aws-lambda-container-image-converter $DPL_FILE_FLAGS --body="See the [changelog](CHANGELOG.md) for details about the changes included in this release." 35 | finally: 36 | - rm -f .git/credentials 37 | -------------------------------------------------------------------------------- /buildspec_stage_release.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | install: 5 | runtime-versions: 6 | golang: 1.13 7 | nodejs: 10 8 | commands: 9 | - npm install -g standard-version@^7.0.0 10 | - git config user.name "Release Automation" 11 | - git config user.email $COMMIT_EMAIL_ADDRESS 12 | 13 | build: 14 | commands: 15 | # Prevent generating a new release if there are no new commits 16 | - export CURRENT_VERSION=`cat VERSION` 17 | - export COMMITS_TO_RELEASE=`git log --pretty=oneline $CURRENT_VERSION..HEAD | wc -l` 18 | - |- 19 | if [ $COMMITS_TO_RELEASE -eq 0 ] 20 | then 21 | echo No changes to release! 22 | echo Current release: $CURRENT_VERSION 23 | exit 1 24 | fi 25 | 26 | # Generate a new version number, changelog entry, and version tag, based on conventional commits since the last release 27 | - standard-version 28 | 29 | # Print information about the release for review. Prevent major version bumps (these warrant extra scrutiny) 30 | - export NEW_VERSION=`cat VERSION` 31 | - export GIT_COMMIT_ID=`git rev-parse HEAD` 32 | - "echo Commit ID to release: $GIT_COMMIT_ID" 33 | - |- 34 | if echo "$NEW_VERSION" | grep -q "^1\." 35 | then 36 | echo New version: $NEW_VERSION 37 | echo Previous version: $CURRENT_VERSION 38 | echo Changes to release: 39 | git log --pretty=oneline $CURRENT_VERSION..$NEW_VERSION 40 | else 41 | echo Uh oh, new major version! $NEW_VERSION 42 | exit 1 43 | fi 44 | 45 | # Build release binaries 46 | - make stage-release-binaries 47 | - "echo Built artifacts:" 48 | - ls -lah ./bin/release-$NEW_VERSION 49 | - ./bin/release-$NEW_VERSION/linux-amd64-img2lambda --version 50 | 51 | # Generate release manifest 52 | - export MANIFESTFILE="$GIT_COMMIT_ID.manifest" 53 | - echo bin/release-$NEW_VERSION/linux-amd64-img2lambda >> $MANIFESTFILE 54 | - echo bin/release-$NEW_VERSION/darwin-amd64-img2lambda >> $MANIFESTFILE 55 | - echo bin/release-$NEW_VERSION/windows-amd64-img2lambda.exe >> $MANIFESTFILE 56 | 57 | artifacts: 58 | files: 59 | - '**/*' 60 | -------------------------------------------------------------------------------- /demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . ../demo-magic/demo-magic.sh 4 | 5 | # Clean up the account 6 | 7 | AWS_ACCOUNT_ID=`aws sts get-caller-identity --query 'Account' --output text` 8 | 9 | aws lambda delete-function --region us-east-1 --function-name php-example-hello 10 | 11 | for layerName in $(aws lambda list-layers --region us-east-1 | jq -r '.Layers[] | select(.LayerName | contains("img2lambda")) | .LayerName'); do 12 | for version in $(aws lambda list-layer-versions --region us-east-1 --layer-name "$layerName" | jq -r '.LayerVersions[].Version'); do 13 | aws lambda delete-layer-version --region us-east-1 --layer-name "$layerName" --version-number "$version" 1>&2 14 | done 15 | done 16 | 17 | cd example/ 18 | 19 | clear 20 | 21 | # Look and feel 22 | 23 | TYPE_SPEED=15 24 | DEMO_COMMENT_COLOR=$CYAN 25 | NO_WAIT=false 26 | 27 | # Start the demo 28 | 29 | PROMPT_TIMEOUT=0 30 | 31 | p "# Welcome to img2lambda!" 32 | 33 | PROMPT_TIMEOUT=1 34 | 35 | p "# Let's create a custom PHP runtime and a PHP function for AWS Lambda using Docker" 36 | 37 | pe "less Dockerfile" 38 | 39 | pe "docker build -t lambda-php ." 40 | 41 | p "# Love that fast Docker build caching!" 42 | 43 | p "# Let's test our PHP runtime and function locally with Docker" 44 | 45 | pe "docker run lambda-php hello '{\"name\": \"World\"}'" 46 | 47 | p "# img2lambda will extract our PHP function from the Docker image, and zip the files into a Lambda deployment package" 48 | 49 | p "# img2lambda will also extract our PHP runtime from the Docker image as Lambda layers, and publish the layers to Lambda" 50 | 51 | pe "img2lambda -i lambda-php:latest -r us-east-1 -o ./output" 52 | 53 | p "# img2lambda found our PHP function files in the Docker image and zipped them up" 54 | 55 | pe "unzip -l output/function.zip" 56 | 57 | p "# img2lambda also found 2 layers in the Docker image that contain our PHP runtime files, so it created 2 Lambda layers and published them" 58 | 59 | pe "more output/layers.json" 60 | 61 | p "# Now we can create a Lambda function that uses the deployment package and the published layers" 62 | 63 | TYPE_SPEED='' 64 | pe "aws lambda create-function --function-name php-example-hello --zip-file fileb://./output/function.zip --layers file://./output/layers.json --runtime provided --handler hello --role \"arn:aws:iam::$AWS_ACCOUNT_ID:role/service-role/LambdaPhpExample\" --region us-east-1" 65 | TYPE_SPEED=15 66 | 67 | p "# Let's now invoke the function and test out our PHP custom runtime" 68 | 69 | TYPE_SPEED='' 70 | pe "aws lambda invoke --function-name php-example-hello --payload '{\"name\": \"World\"}' --region us-east-1 --log-type Tail --query 'LogResult' --output text hello-output.txt | base64 --decode" 71 | TYPE_SPEED=15 72 | 73 | pe "more hello-output.txt" 74 | 75 | p "# Enjoy building your Lambda functions and layers with Docker and img2lambda!" 76 | -------------------------------------------------------------------------------- /example/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | ####### PHP custom runtime ####### 5 | 6 | ####### Install and compile everything ####### 7 | 8 | # Same AL version as Lambda execution environment AMI 9 | FROM amazonlinux:2018.03.0.20190514 as builder 10 | 11 | # Set desired PHP Version 12 | ARG php_version="7.3.6" 13 | 14 | # Lock to 2018.03 release (same as Lambda) and install compilation dependencies 15 | RUN sed -i 's;^releasever.*;releasever=2018.03;;' /etc/yum.conf && \ 16 | yum clean all && \ 17 | yum install -y autoconf \ 18 | bison \ 19 | bzip2-devel \ 20 | gcc \ 21 | gcc-c++ \ 22 | git \ 23 | gzip \ 24 | libcurl-devel \ 25 | libxml2-devel \ 26 | make \ 27 | openssl-devel \ 28 | tar \ 29 | unzip \ 30 | zip 31 | 32 | # Download the PHP source, compile, and install both PHP and Composer 33 | RUN curl -sL https://github.com/php/php-src/archive/php-${php_version}.tar.gz | tar -xvz && \ 34 | cd php-src-php-${php_version} && \ 35 | ./buildconf --force && \ 36 | ./configure --prefix=/opt/php-7-bin/ --with-openssl --with-curl --with-zlib --without-pear --enable-bcmath --with-bz2 --enable-mbstring --with-mysqli && \ 37 | make -j 5 && \ 38 | make install && \ 39 | /opt/php-7-bin/bin/php -v && \ 40 | curl -sS https://getcomposer.org/installer | /opt/php-7-bin/bin/php -- --install-dir=/opt/php-7-bin/bin/ --filename=composer 41 | 42 | # Prepare runtime files 43 | RUN mkdir -p /lambda-php-runtime/bin && \ 44 | cp /opt/php-7-bin/bin/php /lambda-php-runtime/bin/php 45 | 46 | COPY runtime/bootstrap /lambda-php-runtime/ 47 | RUN chmod 0555 /lambda-php-runtime/bootstrap 48 | 49 | # Install Guzzle, prepare vendor files 50 | RUN mkdir /lambda-php-vendor && \ 51 | cd /lambda-php-vendor && \ 52 | /opt/php-7-bin/bin/php /opt/php-7-bin/bin/composer require guzzlehttp/guzzle 53 | 54 | ###### Create runtime image ###### 55 | 56 | FROM lambci/lambda:provided as runtime 57 | 58 | # Layer 1 59 | COPY --from=builder /lambda-php-runtime /opt/ 60 | 61 | # Layer 2 62 | COPY --from=builder /lambda-php-vendor/vendor /opt/vendor 63 | 64 | ###### Create function image ###### 65 | 66 | FROM runtime as function 67 | 68 | COPY function/src /var/task/src/ 69 | -------------------------------------------------------------------------------- /example/deploy/serverless.yml: -------------------------------------------------------------------------------- 1 | service: php-example-hello 2 | 3 | provider: 4 | name: aws 5 | runtime: provided 6 | 7 | package: 8 | individually: true 9 | 10 | functions: 11 | hello: 12 | handler: hello.hello 13 | package: 14 | artifact: ../output/function.zip 15 | layers: 16 | ${file(../output/layers.json)} 17 | goodbye: 18 | handler: goodbye.goodbye 19 | package: 20 | artifact: ../output/function.zip 21 | layers: 22 | ${file(../output/layers.json)} 23 | -------------------------------------------------------------------------------- /example/deploy/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Transform": "AWS::Serverless-2016-10-31", 4 | "Resources": { 5 | "Hello": { 6 | "Type": "AWS::Serverless::Function", 7 | "Properties": { 8 | "FunctionName": "sam-php-example-hello", 9 | "Handler": "hello.hello", 10 | "Runtime": "provided", 11 | "CodeUri": "../output/function.zip", 12 | "Layers": "LAYERS_PLACEHOLDER" 13 | } 14 | }, 15 | "Goodbye": { 16 | "Type": "AWS::Serverless::Function", 17 | "Properties": { 18 | "FunctionName": "sam-php-example-goodbye", 19 | "Handler": "goodbye.goodbye", 20 | "Runtime": "provided", 21 | "CodeUri": "../output/function.zip", 22 | "Layers": "LAYERS_PLACEHOLDER" 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /example/deploy/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: 'AWS::Serverless-2016-10-31' 3 | 4 | Resources: 5 | Hello: 6 | Type: 'AWS::Serverless::Function' 7 | Properties: 8 | FunctionName: sam-php-example-hello 9 | Handler: hello.hello 10 | Runtime: provided 11 | CodeUri: ../output/function.zip 12 | Layers: LAYERS_PLACEHOLDER 13 | 14 | Goodbye: 15 | Type: 'AWS::Serverless::Function' 16 | Properties: 17 | FunctionName: sam-php-example-goodbye 18 | Handler: goodbye.goodbye 19 | Runtime: provided 20 | CodeUri: ../output/function.zip 21 | Layers: LAYERS_PLACEHOLDER 22 | -------------------------------------------------------------------------------- /example/function/src/goodbye.php: -------------------------------------------------------------------------------- 1 | "Goodbye, {$data['name']}!", 10 | 'data' => $data 11 | ]; 12 | return $response; 13 | } 14 | -------------------------------------------------------------------------------- /example/function/src/hello.php: -------------------------------------------------------------------------------- 1 | "Hello, {$data['name']}!", 10 | 'data' => $data 11 | ]; 12 | return $response; 13 | } 14 | -------------------------------------------------------------------------------- /example/runtime/bootstrap: -------------------------------------------------------------------------------- 1 | #!/opt/bin/php 2 | get('http://' . $_ENV['AWS_LAMBDA_RUNTIME_API'] . '/2018-06-01/runtime/invocation/next'); 14 | 15 | return [ 16 | 'invocationId' => $response->getHeader('Lambda-Runtime-Aws-Request-Id')[0], 17 | 'payload' => json_decode((string) $response->getBody(), true) 18 | ]; 19 | } 20 | 21 | function sendResponse($invocationId, $response) 22 | { 23 | $client = new \GuzzleHttp\Client(); 24 | $client->post( 25 | 'http://' . $_ENV['AWS_LAMBDA_RUNTIME_API'] . '/2018-06-01/runtime/invocation/' . $invocationId . '/response', 26 | ['body' => json_encode($response)] 27 | ); 28 | } 29 | 30 | // This is the request processing loop. Barring unrecoverable failure, this loop runs until the environment shuts down. 31 | do { 32 | // Ask the runtime API for a request to handle. 33 | $request = getNextRequest(); 34 | 35 | // Obtain the function name from the _HANDLER environment variable and ensure the function's code is available. 36 | $handlerFunction = array_slice(explode('.', $_ENV['_HANDLER']), -1)[0]; 37 | require_once $_ENV['LAMBDA_TASK_ROOT'] . '/src/' . $handlerFunction . '.php'; 38 | 39 | // Execute the desired function and obtain the response. 40 | $response = $handlerFunction($request['payload']); 41 | 42 | // Submit the response back to the runtime API. 43 | sendResponse($request['invocationId'], $response); 44 | } while (true); 45 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/awslabs/aws-lambda-container-image-converter 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.35.19 7 | github.com/containers/image/v5 v5.8.1 8 | github.com/dsnet/compress v0.0.1 // indirect 9 | github.com/frankban/quicktest v1.7.2 // indirect 10 | github.com/golang/mock v1.4.4 11 | github.com/golang/snappy v0.0.1 // indirect 12 | github.com/mattn/go-zglob v0.0.3 13 | github.com/mholt/archiver v3.1.1+incompatible 14 | github.com/nwaples/rardecode v1.0.0 // indirect 15 | github.com/opencontainers/go-digest v1.0.0 16 | github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 17 | github.com/pierrec/lz4 v2.4.0+incompatible // indirect 18 | github.com/pkg/errors v0.9.1 19 | github.com/stretchr/testify v1.6.1 20 | github.com/urfave/cli v1.22.5 21 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 22 | gopkg.in/yaml.v2 v2.4.0 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774 h1:SCbEWT58NSt7d2mcFdvxC9uyrdcTfvBbPLThhkDmXzg= 3 | github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0= 4 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= 5 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 6 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 7 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 8 | github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA= 9 | github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= 10 | github.com/Microsoft/hcsshim v0.8.9 h1:VrfodqvztU8YSOvygU+DN1BGaSGxmrNfqOv5oOuX2Bk= 11 | github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= 12 | github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= 13 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= 14 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 15 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 16 | github.com/aws/aws-sdk-go v1.35.19 h1:vdIqQnOIqTNtvnOdt9r3Bf/FiCJ7KV/7O2BIj4TPx2w= 17 | github.com/aws/aws-sdk-go v1.35.19/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= 18 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 19 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 20 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 21 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 22 | github.com/checkpoint-restore/go-criu/v4 v4.0.2/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= 23 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 24 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 25 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 26 | github.com/cilium/ebpf v0.0.0-20200507155900-a9f01edf17e3/go.mod h1:XT+cAw5wfvsodedcijoh1l9cf7v1x9FlFB/3VmF/O8s= 27 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 28 | github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s= 29 | github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= 30 | github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= 31 | github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= 32 | github.com/containerd/containerd v1.3.2 h1:ForxmXkA6tPIvffbrDAcPUIB32QgXkt2XFj+F0UxetA= 33 | github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= 34 | github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8= 35 | github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= 36 | github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= 37 | github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= 38 | github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= 39 | github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= 40 | github.com/containers/image/v5 v5.8.1 h1:aHW8a/Kd0dTJ7PTL/fc6y12sJqHxWgqilu+XyHfjD8Q= 41 | github.com/containers/image/v5 v5.8.1/go.mod h1:blOEFd/iFdeyh891ByhCVUc+xAcaI3gBegXECwz9UbQ= 42 | github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= 43 | github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= 44 | github.com/containers/ocicrypt v1.0.3 h1:vYgl+RZ9Q3DPMuTfxmN+qp0X2Bj52uuY2vnt6GzVe1c= 45 | github.com/containers/ocicrypt v1.0.3/go.mod h1:CUBa+8MRNL/VkpxYIpaMtgn1WgXGyvPQj8jcy0EVG6g= 46 | github.com/containers/storage v1.23.7 h1:43ImvG/npvQSZXRjaudVvKISIuZSfI6qvtSNQQSGO/A= 47 | github.com/containers/storage v1.23.7/go.mod h1:cUT2zHjtx+WlVri30obWmM2gpqpi8jfPsmIzP1TVpEI= 48 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 49 | github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= 50 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= 51 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 52 | github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= 53 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 54 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 55 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 56 | github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= 57 | github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 58 | github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f h1:Sm8iD2lifO31DwXfkGzq8VgA7rwxPjRsYmeo0K/dF9Y= 59 | github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 60 | github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= 61 | github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= 62 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= 63 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 64 | github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= 65 | github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= 66 | github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= 67 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 68 | github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= 69 | github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= 70 | github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= 71 | github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= 72 | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= 73 | github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk= 74 | github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= 75 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 76 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 77 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 78 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 79 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 80 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 81 | github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= 82 | github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 83 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 84 | github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= 85 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 86 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 87 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 88 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 89 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 90 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 91 | github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= 92 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 93 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 94 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 95 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 96 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 97 | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= 98 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 99 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 100 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 101 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 102 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 103 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 104 | github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= 105 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 106 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 107 | github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= 108 | github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 109 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 110 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 111 | github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= 112 | github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= 113 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= 114 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 115 | github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= 116 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 117 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 118 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 119 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 120 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 121 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 122 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 123 | github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= 124 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 125 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 126 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 127 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 128 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 129 | github.com/klauspost/compress v1.11.1 h1:bPb7nMRdOZYDrpPMTA3EInUQrdgoBinqUuSwlGdKDdE= 130 | github.com/klauspost/compress v1.11.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 131 | github.com/klauspost/compress v1.11.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc= 132 | github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 133 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 134 | github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= 135 | github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 136 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 137 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 138 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= 139 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 140 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 141 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 142 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 143 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 144 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 145 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 146 | github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= 147 | github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= 148 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 149 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 150 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 151 | github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= 152 | github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= 153 | github.com/mattn/go-zglob v0.0.3 h1:6Ry4EYsScDyt5di4OI6xw1bYhOqfE5S33Z1OPy+d+To= 154 | github.com/mattn/go-zglob v0.0.3/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= 155 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 156 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 157 | github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU= 158 | github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU= 159 | github.com/mistifyio/go-zfs v2.1.1+incompatible h1:gAMO1HM9xBRONLHHYnu5iFsOJUiJdNZo6oqSENd4eW8= 160 | github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= 161 | github.com/moby/sys/mountinfo v0.1.3 h1:KIrhRO14+AkwKvG/g2yIpNMOUVZ02xNhOw8KY1WsLOI= 162 | github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= 163 | github.com/moby/sys/mountinfo v0.4.0 h1:1KInV3Huv18akCu58V7lzNlt+jFmqlu1EaErnEHE/VM= 164 | github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= 165 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 166 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 167 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 168 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 169 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 170 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 171 | github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= 172 | github.com/mtrmac/gpgme v0.1.2/go.mod h1:GYYHnGSuS7HK3zVS2n3y73y0okK/BeKzwnn5jgiVFNI= 173 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 174 | github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= 175 | github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= 176 | github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 177 | github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= 178 | github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 179 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 180 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 181 | github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= 182 | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 183 | github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 h1:yN8BPXVwMBAm3Cuvh1L5XE8XpvYRMdsVLd82ILprhUU= 184 | github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 185 | github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= 186 | github.com/opencontainers/runc v1.0.0-rc91 h1:Tp8LWs5G8rFpzTsbRjAtQkPVexhCu0bnANE5IfIhJ6g= 187 | github.com/opencontainers/runc v1.0.0-rc91/go.mod h1:3Sm6Dt7OT8z88EbdQqqcRN2oCT54jbi72tT/HqgflT8= 188 | github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700 h1:eNUVfm/RFLIi1G7flU5/ZRTHvd4kcVuzfRnL6OFlzCI= 189 | github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 190 | github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2 h1:9mv9SC7GWmRWE0J/+oD8w3GsN2KYGKtg6uwLN7hfP5E= 191 | github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 192 | github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= 193 | github.com/opencontainers/selinux v1.6.0 h1:+bIAS/Za3q5FTwWym4fTB0vObnfCf3G/NC7K6Jx62mY= 194 | github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= 195 | github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw= 196 | github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc= 197 | github.com/pierrec/lz4 v2.4.0+incompatible h1:06usnXXDNcPvCHDkmPpkidf4jTc52UKld7UPfqKatY4= 198 | github.com/pierrec/lz4 v2.4.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 199 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 200 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 201 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 202 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 203 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 204 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 205 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 206 | github.com/pquerna/ffjson v0.0.0-20181028064349-e517b90714f7 h1:gGBSHPOU7g8YjTbhwn+lvFm2VDEhhA+PwDIlstkgSxE= 207 | github.com/pquerna/ffjson v0.0.0-20181028064349-e517b90714f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= 208 | github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9 h1:kyf9snWXHvQc+yxE9imhdI8YAm4oKeZISlaAR+x73zs= 209 | github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= 210 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 211 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 212 | github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= 213 | github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= 214 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 215 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= 216 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 217 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 218 | github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= 219 | github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= 220 | github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 221 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 222 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 223 | github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= 224 | github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= 225 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 226 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 227 | github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= 228 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 229 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 230 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 231 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 232 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 233 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 234 | github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= 235 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 236 | github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= 237 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 238 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 239 | github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= 240 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 241 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 242 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 243 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 244 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 245 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 246 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 247 | github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8= 248 | github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= 249 | github.com/tchap/go-patricia v2.3.0+incompatible h1:GkY4dP3cEfEASBPPkWd+AmjYxhmDkqO9/zg7R0lSQRs= 250 | github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= 251 | github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= 252 | github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= 253 | github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= 254 | github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 255 | github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 256 | github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 257 | github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= 258 | github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 259 | github.com/vbatts/tar-split v0.11.1 h1:0Odu65rhcZ3JZaPHxl7tCI3V/C/Q9Zf82UFravl02dE= 260 | github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g= 261 | github.com/vbauerster/mpb/v5 v5.3.0/go.mod h1:4yTkvAb8Cm4eylAp6t0JRq6pXDkFJ4krUlDqWYkakAs= 262 | github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= 263 | github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= 264 | github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243 h1:R43TdZy32XXSXjJn7M/HhALJ9imq6ztLnChfYJpVDnM= 265 | github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= 266 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 267 | github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 268 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 269 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 270 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= 271 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= 272 | go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= 273 | go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= 274 | go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= 275 | go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= 276 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 277 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 278 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 279 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 280 | golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 281 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 282 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 283 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 284 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 285 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 286 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 287 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 288 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 289 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 290 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 291 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 292 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 293 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 294 | golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 295 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 296 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= 297 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 298 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 299 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 300 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 301 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 302 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 303 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 304 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 305 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 306 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 307 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 308 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 309 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 310 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 311 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 312 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 313 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 314 | golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 315 | golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 316 | golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 317 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo= 318 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 319 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 320 | golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 321 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 322 | golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 323 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 324 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 325 | golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 326 | golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed h1:WBkVNH1zd9jg/dK4HCM4lNANnmd12EHC9z+LmcCG4ns= 327 | golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 328 | golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= 329 | golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 330 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 331 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 332 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 333 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 334 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= 335 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 336 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 337 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 338 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 339 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 340 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 341 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 342 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262 h1:qsl9y/CJx34tuA7QCPNp86JNJe4spst6Ff8MjvPUdPg= 343 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 344 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 345 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 346 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 347 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 348 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 349 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb h1:i1Ppqkc3WQXikh8bXiwHqAN5Rv3/qDCcRk0/Otx73BY= 350 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 351 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg= 352 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 353 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 354 | google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU= 355 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 356 | google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk= 357 | google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 358 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 359 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 360 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 361 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 362 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 363 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 364 | gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 365 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 366 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 367 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 368 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 369 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 370 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 371 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 372 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 373 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 374 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 375 | gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= 376 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 377 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 378 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 379 | -------------------------------------------------------------------------------- /img2lambda/cli/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | package main 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "log" 9 | "os" 10 | "strings" 11 | 12 | "github.com/awslabs/aws-lambda-container-image-converter/img2lambda/extract" 13 | "github.com/awslabs/aws-lambda-container-image-converter/img2lambda/publish" 14 | "github.com/awslabs/aws-lambda-container-image-converter/img2lambda/types" 15 | "github.com/awslabs/aws-lambda-container-image-converter/img2lambda/version" 16 | "github.com/urfave/cli" 17 | ) 18 | 19 | func createApp() (*cli.App, *types.CmdOptions) { 20 | opts := types.CmdOptions{} 21 | 22 | app := cli.NewApp() 23 | app.EnableBashCompletion = true 24 | app.Name = "img2lambda" 25 | app.Version = version.VersionString() 26 | app.Usage = "Repackages a container image into an AWS Lambda function deployment package. Extracts AWS Lambda layers from the image and publishes them to Lambda" 27 | app.Action = func(c *cli.Context) error { 28 | // parse and store the passed runtime list into the options object 29 | opts.CompatibleRuntimes = c.StringSlice("cr") 30 | 31 | validateCliOptions(&opts, c) 32 | return repackImageAction(&opts, c) 33 | } 34 | app.Flags = []cli.Flag{ 35 | cli.StringFlag{ 36 | Name: "image, i", 37 | Usage: "Name or path of the source container image. For example, 'my-docker-image:latest' or './my-oci-image-archive'. The image must be pulled locally already.", 38 | Destination: &opts.Image, 39 | }, 40 | cli.StringFlag{ 41 | Name: "image-type, t", 42 | Usage: "Type of the source container image. Valid values: 'docker' (Docker image from the local Docker daemon), 'oci' (OCI image archive at the given path)", 43 | Value: "docker", 44 | Destination: &opts.ImageType, 45 | }, 46 | cli.StringFlag{ 47 | Name: "region, r", 48 | Usage: "AWS region", 49 | Value: "us-east-1", 50 | Destination: &opts.Region, 51 | }, 52 | cli.StringFlag{ 53 | Name: "profile, p", 54 | Usage: "AWS credentials profile. Credentials will default to the same chain as the AWS CLI: environment variables, default profile, container credentials, EC2 instance credentials", 55 | Destination: &opts.Profile, 56 | }, 57 | cli.StringFlag{ 58 | Name: "output-directory, o", 59 | Usage: "Destination directory for output: function deployment package (function.zip) and list of published layers (layers.json, layers.yaml)", 60 | Value: "./output", 61 | Destination: &opts.OutputDir, 62 | }, 63 | cli.StringFlag{ 64 | Name: "layer-namespace, n", 65 | Usage: "Prefix for the layers published to Lambda", 66 | Value: "img2lambda", 67 | Destination: &opts.LayerNamespace, 68 | }, 69 | cli.BoolFlag{ 70 | Name: "dry-run, d", 71 | Usage: "Conduct a dry-run: Repackage the image, but only write the Lambda layers to local disk (do not publish to Lambda)", 72 | Destination: &opts.DryRun, 73 | }, 74 | cli.StringFlag{ 75 | Name: "description, desc", 76 | Usage: "The description of this layer version (default: \"created by img2lambda from image \")", 77 | Destination: &opts.Description, 78 | }, 79 | cli.StringFlag{ 80 | Name: "license-info, l", 81 | Usage: "The layer's software license. It can be an SPDX license identifier, the URL of the license hosted on the internet, or the full text of the license (default: no license)", 82 | Destination: &opts.LicenseInfo, 83 | }, 84 | cli.StringSliceFlag{ 85 | Name: "compatible-runtime, cr", 86 | Usage: "An AWS Lambda function runtime compatible with the image layers. To specify multiple runtimes, repeat the option: --cr provided --cr python2.7 (default: \"provided\")", 87 | Value: &cli.StringSlice{}, 88 | }, 89 | } 90 | 91 | app.Setup() 92 | app.Commands = []cli.Command{} 93 | 94 | return app, &opts 95 | } 96 | 97 | func validateCliOptions(opts *types.CmdOptions, context *cli.Context) { 98 | if opts.Image == "" { 99 | fmt.Print("ERROR: Image name is required\n\n") 100 | cli.ShowAppHelpAndExit(context, 1) 101 | } 102 | 103 | for _, runtime := range opts.CompatibleRuntimes { 104 | if !types.ValidRuntimes.Contains(runtime) { 105 | fmt.Println("ERROR: Compatible runtimes must be one of the supported runtimes\n\n", types.ValidRuntimes) 106 | cli.ShowAppHelpAndExit(context, 1) 107 | } 108 | } 109 | } 110 | 111 | func repackImageAction(opts *types.CmdOptions, context *cli.Context) error { 112 | var imageTransport string 113 | switch opts.ImageType { 114 | case "docker": 115 | imageTransport = "docker-daemon:" 116 | case "oci": 117 | imageTransport = "oci-archive:" 118 | default: 119 | fmt.Println("ERROR: Image type must be one of the supported image types") 120 | cli.ShowAppHelpAndExit(context, 1) 121 | } 122 | 123 | imageLocation := imageTransport + opts.Image 124 | if opts.ImageType == "docker" && strings.Count(imageLocation, ":") == 1 { 125 | imageLocation += ":latest" 126 | } 127 | 128 | layers, function, err := extract.RepackImage(imageLocation, opts.OutputDir) 129 | if err != nil { 130 | return err 131 | } 132 | 133 | if function.FileCount == 0 { 134 | // remove empty zip file 135 | os.Remove(function.File) 136 | } 137 | 138 | if len(layers) == 0 && function.FileCount == 0 { 139 | return errors.New("No compatible layers or function files found in the image (likely nothing found in /opt and /var/task)") 140 | } 141 | 142 | if !opts.DryRun { 143 | _, _, err := publish.PublishLambdaLayers(types.ConvertToPublishOptions(opts), layers) 144 | if err != nil { 145 | return err 146 | } 147 | } 148 | 149 | return nil 150 | } 151 | 152 | func main() { 153 | app, _ := createApp() 154 | err := app.Run(os.Args) 155 | if err != nil { 156 | log.Fatal(err) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /img2lambda/clients/clients.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | package clients 4 | 5 | import ( 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/request" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/lambda" 10 | "github.com/awslabs/aws-lambda-container-image-converter/img2lambda/version" 11 | ) 12 | 13 | var userAgentHandler = request.NamedHandler{ 14 | Name: "img2lambda.UserAgentHandler", 15 | Fn: request.MakeAddToUserAgentHandler("aws-lambda-container-image-converter", version.Version), 16 | } 17 | 18 | func NewLambdaClient(region string, profile string) *lambda.Lambda { 19 | sess := session.Must(session.NewSessionWithOptions(session.Options{ 20 | Profile: profile, 21 | SharedConfigState: session.SharedConfigEnable, 22 | })) 23 | sess.Handlers.Build.PushBackNamed(userAgentHandler) 24 | 25 | client := lambda.New(sess, &aws.Config{Region: aws.String(region)}) 26 | 27 | return client 28 | } 29 | -------------------------------------------------------------------------------- /img2lambda/extract/repack_image.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | package extract 4 | 5 | import ( 6 | "archive/tar" 7 | "compress/gzip" 8 | "context" 9 | "fmt" 10 | "io" 11 | "log" 12 | "os" 13 | "path/filepath" 14 | "strings" 15 | 16 | "github.com/awslabs/aws-lambda-container-image-converter/img2lambda/types" 17 | "github.com/containers/image/v5/image" 18 | "github.com/containers/image/v5/pkg/blobinfocache" 19 | "github.com/containers/image/v5/transports/alltransports" 20 | imgtypes "github.com/containers/image/v5/types" 21 | zglob "github.com/mattn/go-zglob" 22 | "github.com/mholt/archiver" 23 | "github.com/pkg/errors" 24 | ) 25 | 26 | // Converts container image to Lambda layer and function deployment package archive files 27 | func RepackImage(imageName string, layerOutputDir string) (layers []types.LambdaLayer, function *types.LambdaDeploymentPackage, retErr error) { 28 | log.Printf("Parsing the image %s", imageName) 29 | 30 | // Get image's layer data from image name 31 | ref, err := alltransports.ParseImageName(imageName) 32 | if err != nil { 33 | return nil, nil, err 34 | } 35 | 36 | sys := &imgtypes.SystemContext{} 37 | 38 | dockerHost := os.Getenv("DOCKER_HOST") 39 | 40 | // Support communicating with Docker for Windows over local plain-text TCP socket 41 | if dockerHost == "tcp://localhost:2375" || dockerHost == "tcp://127.0.0.1:2375" { 42 | sys.DockerDaemonHost = strings.Replace(dockerHost, "tcp://", "http://", -1) 43 | } 44 | 45 | // Support communicating with Docker Toolbox over encrypted socket 46 | if strings.HasPrefix(dockerHost, "tcp://192.168.") && strings.HasSuffix(dockerHost, ":2376") { 47 | sys.DockerDaemonHost = strings.Replace(dockerHost, "tcp://", "https://", -1) 48 | } 49 | 50 | ctx := context.Background() 51 | 52 | cache := blobinfocache.DefaultCache(sys) 53 | 54 | rawSource, err := ref.NewImageSource(ctx, sys) 55 | if err != nil { 56 | return nil, nil, err 57 | } 58 | 59 | src, err := image.FromSource(ctx, sys, rawSource) 60 | if err != nil { 61 | if closeErr := rawSource.Close(); closeErr != nil { 62 | return nil, nil, errors.Wrapf(err, " (close error: %v)", closeErr) 63 | } 64 | 65 | return nil, nil, err 66 | } 67 | defer func() { 68 | if err := src.Close(); err != nil { 69 | retErr = errors.Wrapf(retErr, " (close error: %v)", err) 70 | } 71 | }() 72 | 73 | return repackImage(&repackOptions{ 74 | ctx: ctx, 75 | cache: cache, 76 | imageSource: src, 77 | rawImageSource: rawSource, 78 | imageName: imageName, 79 | layerOutputDir: layerOutputDir, 80 | }) 81 | } 82 | 83 | type repackOptions struct { 84 | ctx context.Context 85 | cache imgtypes.BlobInfoCache 86 | imageSource imgtypes.ImageCloser 87 | rawImageSource imgtypes.ImageSource 88 | imageName string 89 | layerOutputDir string 90 | } 91 | 92 | func repackImage(opts *repackOptions) (layers []types.LambdaLayer, function *types.LambdaDeploymentPackage, retErr error) { 93 | 94 | layerInfos := opts.imageSource.LayerInfos() 95 | 96 | log.Printf("Image %s has %d layers", opts.imageName, len(layerInfos)) 97 | 98 | // Unpack and inspect each image layer, copy relevant files to new Lambda layer or to a Lambda deployment package 99 | if err := os.MkdirAll(opts.layerOutputDir, 0755); err != nil { 100 | return nil, nil, err 101 | } 102 | 103 | function = &types.LambdaDeploymentPackage{FileCount: 0, File: filepath.Join(opts.layerOutputDir, "function.zip")} 104 | functionZip, functionFile, err := startZipFile(function.File) 105 | if err != nil { 106 | return nil, nil, fmt.Errorf("starting zip file: %v", err) 107 | } 108 | defer func() { 109 | if err := functionZip.Close(); err != nil { 110 | retErr = errors.Wrapf(err, " (zip close error: %v)", err) 111 | } 112 | if err := functionFile.Close(); err != nil { 113 | retErr = errors.Wrapf(err, " (file close error: %v)", err) 114 | } 115 | }() 116 | 117 | lambdaLayerNum := 1 118 | 119 | for _, layerInfo := range layerInfos { 120 | lambdaLayerFilename := filepath.Join(opts.layerOutputDir, fmt.Sprintf("layer-%d.zip", lambdaLayerNum)) 121 | 122 | layerStream, _, err := opts.rawImageSource.GetBlob(opts.ctx, layerInfo, opts.cache) 123 | if err != nil { 124 | return nil, function, err 125 | } 126 | defer layerStream.Close() 127 | 128 | layerFileCreated, layerFunctionFileCount, err := repackLayer(lambdaLayerFilename, functionZip, layerStream, false) 129 | if err != nil { 130 | tarErr := err 131 | 132 | // tar extraction failed, try tar.gz 133 | layerStream, _, err = opts.rawImageSource.GetBlob(opts.ctx, layerInfo, opts.cache) 134 | if err != nil { 135 | return nil, function, err 136 | } 137 | defer layerStream.Close() 138 | 139 | layerFileCreated, layerFunctionFileCount, err = repackLayer(lambdaLayerFilename, functionZip, layerStream, true) 140 | if err != nil { 141 | return nil, function, fmt.Errorf("could not read layer with tar nor tar.gz: %v, %v", err, tarErr) 142 | } 143 | } 144 | 145 | function.FileCount += layerFunctionFileCount 146 | 147 | if layerFunctionFileCount == 0 { 148 | log.Printf("Did not extract any Lambda function files from image layer %s (no relevant files found)", string(layerInfo.Digest)) 149 | } 150 | 151 | if layerFileCreated { 152 | log.Printf("Created Lambda layer file %s from image layer %s", lambdaLayerFilename, string(layerInfo.Digest)) 153 | lambdaLayerNum++ 154 | layers = append(layers, types.LambdaLayer{Digest: string(layerInfo.Digest), File: lambdaLayerFilename}) 155 | } else { 156 | log.Printf("Did not create a Lambda layer file from image layer %s (no relevant files found)", string(layerInfo.Digest)) 157 | } 158 | } 159 | 160 | log.Printf("Extracted %d Lambda function files for image %s", function.FileCount, opts.imageName) 161 | if function.FileCount > 0 { 162 | log.Printf("Created Lambda function deployment package %s", function.File) 163 | } 164 | log.Printf("Created %d Lambda layer files for image %s", len(layers), opts.imageName) 165 | 166 | return layers, function, retErr 167 | } 168 | 169 | // Converts container image layer archive (tar) to Lambda layer archive (zip). 170 | // Filters files from the source and only writes a new archive if at least 171 | // one file in the source matches the filter (i.e. does not create empty archives). 172 | func repackLayer(outputFilename string, functionZip *archiver.Zip, layerContents io.Reader, isGzip bool) (lambdaLayerCreated bool, functionFileCount int, retError error) { 173 | t := archiver.NewTar() 174 | contentsReader := layerContents 175 | var err error 176 | 177 | if isGzip { 178 | gzr, err := gzip.NewReader(layerContents) 179 | if err != nil { 180 | return false, 0, fmt.Errorf("could not create gzip reader for layer: %v", err) 181 | } 182 | defer gzr.Close() 183 | contentsReader = gzr 184 | } 185 | 186 | err = t.Open(contentsReader, 0) 187 | if err != nil { 188 | return false, 0, fmt.Errorf("opening layer tar: %v", err) 189 | } 190 | defer t.Close() 191 | 192 | // Walk the files in the tar 193 | var z *archiver.Zip 194 | var out *os.File 195 | defer func() { 196 | if z != nil { 197 | if err := z.Close(); err != nil { 198 | retError = errors.Wrapf(err, " (zip close error: %v)", err) 199 | } 200 | } 201 | if out != nil { 202 | if err := out.Close(); err != nil { 203 | retError = errors.Wrapf(err, " (file close error: %v)", err) 204 | } 205 | } 206 | }() 207 | 208 | for { 209 | // Get next file in tar 210 | f, err := t.Read() 211 | if err == io.EOF { 212 | break 213 | } 214 | 215 | if err != nil { 216 | return false, 0, fmt.Errorf("opening next file in layer tar: %v", err) 217 | } 218 | 219 | // Determine if this file should be repacked into a Lambda layer 220 | repack, err := shouldRepackLayerFileToLambdaLayer(f) 221 | if err != nil { 222 | return false, 0, fmt.Errorf("filtering file in layer tar: %v", err) 223 | } 224 | if repack { 225 | if z == nil { 226 | z, out, err = startZipFile(outputFilename) 227 | if err != nil { 228 | return false, 0, fmt.Errorf("starting zip file: %v", err) 229 | } 230 | } 231 | 232 | err = repackLayerFile(f, z) 233 | } 234 | 235 | if err != nil { 236 | return false, 0, fmt.Errorf("walking %s in layer tar: %v", f.Name(), err) 237 | } 238 | 239 | // Determine if this file should be repacked into a Lambda function package 240 | repack, err = shouldRepackLayerFileToLambdaFunction(f) 241 | if err != nil { 242 | return false, 0, fmt.Errorf("filtering file in layer tar: %v", err) 243 | } 244 | if repack { 245 | err = repackLayerFile(f, functionZip) 246 | functionFileCount++ 247 | } 248 | 249 | if err != nil { 250 | return false, 0, fmt.Errorf("walking %s in layer tar: %v", f.Name(), err) 251 | } 252 | } 253 | 254 | return (z != nil), functionFileCount, nil 255 | } 256 | 257 | func startZipFile(destination string) (zip *archiver.Zip, zipFile *os.File, err error) { 258 | z := archiver.NewZip() 259 | 260 | out, err := os.Create(destination) 261 | if err != nil { 262 | return nil, nil, fmt.Errorf("creating %s: %v", destination, err) 263 | } 264 | 265 | err = z.Create(out) 266 | if err != nil { 267 | return nil, nil, fmt.Errorf("creating zip: %v", err) 268 | } 269 | 270 | return z, out, nil 271 | } 272 | 273 | func getLayerFileName(f archiver.File) (name string, err error) { 274 | header, ok := f.Header.(*tar.Header) 275 | if !ok { 276 | return "", fmt.Errorf("expected header to be *tar.Header but was %T", f.Header) 277 | } 278 | 279 | if f.IsDir() || header.Typeflag == tar.TypeDir { 280 | return "", nil 281 | } 282 | 283 | // Ignore whiteout files 284 | if strings.HasPrefix(f.Name(), ".wh.") { 285 | return "", nil 286 | } 287 | 288 | return header.Name, nil 289 | } 290 | 291 | func shouldRepackLayerFileToLambdaLayer(f archiver.File) (should bool, err error) { 292 | filename, err := getLayerFileName(f) 293 | if err != nil { 294 | return false, err 295 | } 296 | if filename == "" { 297 | return false, nil 298 | } 299 | 300 | // Only extract files that can be used for Lambda custom runtimes 301 | return zglob.Match("opt/**/**", filename) 302 | } 303 | 304 | func shouldRepackLayerFileToLambdaFunction(f archiver.File) (should bool, err error) { 305 | filename, err := getLayerFileName(f) 306 | if err != nil { 307 | return false, err 308 | } 309 | if filename == "" { 310 | return false, nil 311 | } 312 | 313 | // Only extract files that can be used for Lambda deployment packages 314 | return zglob.Match("var/task/**/**", filename) 315 | } 316 | 317 | func repackLayerFile(f archiver.File, z *archiver.Zip) error { 318 | hdr, ok := f.Header.(*tar.Header) 319 | if !ok { 320 | return fmt.Errorf("expected header to be *tar.Header but was %T", f.Header) 321 | } 322 | 323 | filename := strings.TrimPrefix(filepath.ToSlash(hdr.Name), "opt/") 324 | filename = strings.TrimPrefix(filename, "var/task/") 325 | 326 | switch hdr.Typeflag { 327 | case tar.TypeReg, tar.TypeRegA, tar.TypeChar, tar.TypeBlock, tar.TypeFifo, tar.TypeSymlink, tar.TypeLink: 328 | return z.Write(archiver.File{ 329 | FileInfo: archiver.FileInfo{ 330 | FileInfo: f.FileInfo, 331 | CustomName: filename, 332 | }, 333 | ReadCloser: f, 334 | }) 335 | case tar.TypeXGlobalHeader: 336 | return nil // ignore 337 | default: 338 | return fmt.Errorf("%s: unknown type flag: %c", hdr.Name, hdr.Typeflag) 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /img2lambda/extract/repack_image_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | package extract 4 | 5 | import ( 6 | "archive/zip" 7 | "bufio" 8 | "bytes" 9 | "context" 10 | "io" 11 | "io/ioutil" 12 | "os" 13 | "testing" 14 | 15 | "github.com/awslabs/aws-lambda-container-image-converter/img2lambda/internal/testing/mocks" 16 | "github.com/awslabs/aws-lambda-container-image-converter/img2lambda/types" 17 | imgtypes "github.com/containers/image/v5/types" 18 | "github.com/golang/mock/gomock" 19 | "github.com/mholt/archiver" 20 | godigest "github.com/opencontainers/go-digest" 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func CreateLayerData(t *testing.T, 25 | filename string, 26 | fileContents string, 27 | digest string) *bytes.Buffer { 28 | tar := archiver.NewTar() 29 | 30 | layerFile, err := ioutil.TempFile("", "") 31 | assert.Nil(t, err) 32 | _, err = layerFile.WriteString(fileContents) 33 | assert.Nil(t, err) 34 | err = layerFile.Close() 35 | assert.Nil(t, err) 36 | layerFileInfo, err := os.Stat(layerFile.Name()) 37 | assert.Nil(t, err) 38 | 39 | var tarContents bytes.Buffer 40 | bufWriter := bufio.NewWriter(&tarContents) 41 | layerFile, err = os.Open(layerFile.Name()) 42 | assert.Nil(t, err) 43 | err = tar.Create(bufWriter) 44 | assert.Nil(t, err) 45 | err = tar.Write(archiver.File{ 46 | FileInfo: archiver.FileInfo{ 47 | FileInfo: layerFileInfo, 48 | CustomName: filename, 49 | }, 50 | ReadCloser: layerFile, 51 | }) 52 | assert.Nil(t, err) 53 | err = bufWriter.Flush() 54 | assert.Nil(t, err) 55 | err = tar.Close() 56 | assert.Nil(t, err) 57 | err = layerFile.Close() 58 | assert.Nil(t, err) 59 | err = os.Remove(layerFile.Name()) 60 | assert.Nil(t, err) 61 | 62 | return &tarContents 63 | } 64 | 65 | func createImageLayer(t *testing.T, 66 | rawSource *mocks.MockImageSource, 67 | filename string, 68 | fileContents string, 69 | digest string) *imgtypes.BlobInfo { 70 | 71 | tarContents := CreateLayerData(t, filename, fileContents, digest) 72 | 73 | blobInfo := imgtypes.BlobInfo{Digest: godigest.Digest(digest)} 74 | 75 | rawSource.EXPECT().GetBlob(gomock.Any(), 76 | blobInfo, 77 | gomock.Any()).Return(ioutil.NopCloser(bytes.NewReader(tarContents.Bytes())), int64(0), nil) 78 | 79 | return &blobInfo 80 | } 81 | 82 | func createGzipImageLayer(t *testing.T, 83 | rawSource *mocks.MockImageSource, 84 | filename string, 85 | fileContents string, 86 | digest string) *imgtypes.BlobInfo { 87 | 88 | tarContents := CreateLayerData(t, filename, fileContents, digest) 89 | 90 | var targzContents bytes.Buffer 91 | bufWriter := bufio.NewWriter(&targzContents) 92 | 93 | gz := archiver.NewGz() 94 | err := gz.Compress(bytes.NewReader(tarContents.Bytes()), bufWriter) 95 | assert.Nil(t, err) 96 | err = bufWriter.Flush() 97 | assert.Nil(t, err) 98 | 99 | blobInfo := imgtypes.BlobInfo{Digest: godigest.Digest(digest)} 100 | 101 | contentsBytes := targzContents.Bytes() 102 | 103 | rawSource.EXPECT().GetBlob(gomock.Any(), 104 | blobInfo, 105 | gomock.Any()). 106 | DoAndReturn( 107 | func(context context.Context, blobInfo imgtypes.BlobInfo, cache imgtypes.BlobInfoCache) (io.ReadCloser, int64, error) { 108 | // checks whatever 109 | return ioutil.NopCloser(bytes.NewReader(contentsBytes)), int64(0), nil 110 | }). 111 | Times(2) 112 | 113 | return &blobInfo 114 | } 115 | 116 | func validateLambdaLayer(t *testing.T, 117 | layer *types.LambdaLayer, 118 | expectedFilename string, 119 | expectedFileContents string, 120 | expectedDigest string) { 121 | 122 | assert.Equal(t, expectedDigest, layer.Digest) 123 | 124 | z := archiver.NewZip() 125 | 126 | zipFile, err := os.Open(layer.File) 127 | assert.Nil(t, err) 128 | zipFileInfo, err := os.Stat(zipFile.Name()) 129 | assert.Nil(t, err) 130 | 131 | err = z.Open(zipFile, zipFileInfo.Size()) 132 | assert.Nil(t, err) 133 | 134 | contentsFile, err := z.Read() 135 | assert.Nil(t, err) 136 | zfh, ok := contentsFile.Header.(zip.FileHeader) 137 | assert.True(t, ok) 138 | assert.Equal(t, expectedFilename, zfh.Name) 139 | 140 | buf := new(bytes.Buffer) 141 | _, err = buf.ReadFrom(contentsFile.ReadCloser) 142 | assert.Nil(t, err) 143 | contents := buf.String() 144 | assert.Equal(t, expectedFileContents, contents) 145 | 146 | _, err = z.Read() 147 | assert.NotNil(t, err) 148 | assert.Equal(t, io.EOF, err) 149 | 150 | err = z.Close() 151 | assert.Nil(t, err) 152 | err = zipFile.Close() 153 | assert.Nil(t, err) 154 | err = os.Remove(zipFile.Name()) 155 | assert.Nil(t, err) 156 | } 157 | 158 | func validateLambdaDeploymentPackage(t *testing.T, 159 | function *types.LambdaDeploymentPackage, 160 | expectedFilenames []string, 161 | expectedFilesContents []string) { 162 | 163 | z := archiver.NewZip() 164 | 165 | zipFile, err := os.Open(function.File) 166 | assert.Nil(t, err) 167 | zipFileInfo, err := os.Stat(zipFile.Name()) 168 | assert.Nil(t, err) 169 | 170 | err = z.Open(zipFile, zipFileInfo.Size()) 171 | assert.Nil(t, err) 172 | 173 | for i := range expectedFilenames { 174 | contentsFile, err := z.Read() 175 | assert.Nil(t, err) 176 | zfh, ok := contentsFile.Header.(zip.FileHeader) 177 | assert.True(t, ok) 178 | assert.Equal(t, expectedFilenames[i], zfh.Name) 179 | 180 | buf := new(bytes.Buffer) 181 | _, err = buf.ReadFrom(contentsFile.ReadCloser) 182 | assert.Nil(t, err) 183 | contents := buf.String() 184 | assert.Equal(t, expectedFilesContents[i], contents) 185 | } 186 | 187 | _, err = z.Read() 188 | assert.NotNil(t, err) 189 | assert.Equal(t, io.EOF, err) 190 | 191 | err = z.Close() 192 | assert.Nil(t, err) 193 | err = zipFile.Close() 194 | assert.Nil(t, err) 195 | err = os.Remove(zipFile.Name()) 196 | assert.Nil(t, err) 197 | } 198 | 199 | func TestRepack(t *testing.T) { 200 | ctrl := gomock.NewController(t) 201 | defer ctrl.Finish() 202 | 203 | source := mocks.NewMockImageCloser(ctrl) 204 | rawSource := mocks.NewMockImageSource(ctrl) 205 | 206 | // Create layer tar files 207 | var blobInfos []imgtypes.BlobInfo 208 | 209 | // First matching file 210 | blobInfo1 := createImageLayer(t, rawSource, "opt/file1", "hello world 1", "digest1") 211 | blobInfos = append(blobInfos, *blobInfo1) 212 | 213 | // Second matching file 214 | blobInfo2 := createGzipImageLayer(t, rawSource, "opt/hello/file2", "hello world 2", "digest2") 215 | blobInfos = append(blobInfos, *blobInfo2) 216 | 217 | // Irrelevant file 218 | blobInfo3 := createImageLayer(t, rawSource, "local/hello", "hello world 3", "digest3") 219 | blobInfos = append(blobInfos, *blobInfo3) 220 | 221 | // Overwriting previous file 222 | blobInfo4 := createImageLayer(t, rawSource, "opt/file1", "hello world 4", "digest4") 223 | blobInfos = append(blobInfos, *blobInfo4) 224 | 225 | // Function file layers 226 | blobInfo5 := createImageLayer(t, rawSource, "var/task/file1", "hello world 5", "digest5") 227 | blobInfos = append(blobInfos, *blobInfo5) 228 | 229 | blobInfo6 := createImageLayer(t, rawSource, "var/task/file2", "hello world 6", "digest6") 230 | blobInfos = append(blobInfos, *blobInfo6) 231 | 232 | blobInfo7 := createImageLayer(t, rawSource, "var/task/file1", "hello world 7", "digest7") 233 | blobInfos = append(blobInfos, *blobInfo7) 234 | 235 | source.EXPECT().LayerInfos().Return(blobInfos) 236 | 237 | dir, err := ioutil.TempDir("", "") 238 | assert.Nil(t, err) 239 | 240 | layers, function, err := repackImage(&repackOptions{ 241 | ctx: nil, 242 | cache: nil, 243 | imageSource: source, 244 | rawImageSource: rawSource, 245 | imageName: "test-image", 246 | layerOutputDir: dir, 247 | }) 248 | 249 | assert.Nil(t, err) 250 | assert.Len(t, layers, 3) 251 | assert.Equal(t, 3, function.FileCount) 252 | 253 | validateLambdaLayer(t, &layers[0], "file1", "hello world 1", "digest1") 254 | validateLambdaLayer(t, &layers[1], "hello/file2", "hello world 2", "digest2") 255 | validateLambdaLayer(t, &layers[2], "file1", "hello world 4", "digest4") 256 | 257 | validateLambdaDeploymentPackage(t, function, 258 | []string{"file1", "file2", "file1"}, 259 | []string{"hello world 5", "hello world 6", "hello world 7"}) 260 | 261 | err = os.Remove(dir) 262 | assert.Nil(t, err) 263 | } 264 | 265 | func TestRepackFailure(t *testing.T) { 266 | ctrl := gomock.NewController(t) 267 | defer ctrl.Finish() 268 | 269 | source := mocks.NewMockImageCloser(ctrl) 270 | rawSource := mocks.NewMockImageSource(ctrl) 271 | 272 | // Create layer tar files 273 | var blobInfos []imgtypes.BlobInfo 274 | 275 | // Add garbage data to layer 276 | blobInfo := imgtypes.BlobInfo{Digest: godigest.Digest("digest1")} 277 | rawSource.EXPECT().GetBlob(gomock.Any(), 278 | blobInfo, 279 | gomock.Any()). 280 | Return(ioutil.NopCloser(bytes.NewReader([]byte("hello world"))), int64(0), nil). 281 | Times(2) 282 | blobInfos = append(blobInfos, blobInfo) 283 | 284 | source.EXPECT().LayerInfos().Return(blobInfos) 285 | 286 | dir, err := ioutil.TempDir("", "") 287 | assert.Nil(t, err) 288 | 289 | layers, function, err := repackImage(&repackOptions{ 290 | ctx: nil, 291 | cache: nil, 292 | imageSource: source, 293 | rawImageSource: rawSource, 294 | imageName: "test-image", 295 | layerOutputDir: dir, 296 | }) 297 | 298 | assert.Nil(t, layers) 299 | assert.Equal(t, 0, function.FileCount) 300 | assert.NotNil(t, err) 301 | assert.Equal(t, err.Error(), "could not read layer with tar nor tar.gz: could not create gzip reader for layer: EOF, opening next file in layer tar: unexpected EOF") 302 | 303 | err = os.Remove(function.File) 304 | assert.Nil(t, err) 305 | 306 | err = os.Remove(dir) 307 | assert.Nil(t, err) 308 | } 309 | -------------------------------------------------------------------------------- /img2lambda/internal/testing/generate_mocks.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | package testing 4 | 5 | //go:generate mockgen.sh github.com/aws/aws-sdk-go/service/lambda/lambdaiface LambdaAPI mocks/lambda_mocks.go 6 | //go:generate mockgen.sh github.com/containers/image/v5/types ImageCloser,ImageSource mocks/image_mocks.go 7 | -------------------------------------------------------------------------------- /img2lambda/internal/testing/mocks/image_mocks.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | // Code generated by MockGen. DO NOT EDIT. 4 | // Source: github.com/containers/image/v5/types (interfaces: ImageCloser,ImageSource) 5 | 6 | // Package mocks is a generated GoMock package. 7 | package mocks 8 | 9 | import ( 10 | context "context" 11 | io "io" 12 | reflect "reflect" 13 | 14 | reference "github.com/containers/image/v5/docker/reference" 15 | types "github.com/containers/image/v5/types" 16 | gomock "github.com/golang/mock/gomock" 17 | go_digest "github.com/opencontainers/go-digest" 18 | v1 "github.com/opencontainers/image-spec/specs-go/v1" 19 | ) 20 | 21 | // MockImageCloser is a mock of ImageCloser interface 22 | type MockImageCloser struct { 23 | ctrl *gomock.Controller 24 | recorder *MockImageCloserMockRecorder 25 | } 26 | 27 | // MockImageCloserMockRecorder is the mock recorder for MockImageCloser 28 | type MockImageCloserMockRecorder struct { 29 | mock *MockImageCloser 30 | } 31 | 32 | // NewMockImageCloser creates a new mock instance 33 | func NewMockImageCloser(ctrl *gomock.Controller) *MockImageCloser { 34 | mock := &MockImageCloser{ctrl: ctrl} 35 | mock.recorder = &MockImageCloserMockRecorder{mock} 36 | return mock 37 | } 38 | 39 | // EXPECT returns an object that allows the caller to indicate expected use 40 | func (m *MockImageCloser) EXPECT() *MockImageCloserMockRecorder { 41 | return m.recorder 42 | } 43 | 44 | // Close mocks base method 45 | func (m *MockImageCloser) Close() error { 46 | m.ctrl.T.Helper() 47 | ret := m.ctrl.Call(m, "Close") 48 | ret0, _ := ret[0].(error) 49 | return ret0 50 | } 51 | 52 | // Close indicates an expected call of Close 53 | func (mr *MockImageCloserMockRecorder) Close() *gomock.Call { 54 | mr.mock.ctrl.T.Helper() 55 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockImageCloser)(nil).Close)) 56 | } 57 | 58 | // ConfigBlob mocks base method 59 | func (m *MockImageCloser) ConfigBlob(arg0 context.Context) ([]byte, error) { 60 | m.ctrl.T.Helper() 61 | ret := m.ctrl.Call(m, "ConfigBlob", arg0) 62 | ret0, _ := ret[0].([]byte) 63 | ret1, _ := ret[1].(error) 64 | return ret0, ret1 65 | } 66 | 67 | // ConfigBlob indicates an expected call of ConfigBlob 68 | func (mr *MockImageCloserMockRecorder) ConfigBlob(arg0 interface{}) *gomock.Call { 69 | mr.mock.ctrl.T.Helper() 70 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigBlob", reflect.TypeOf((*MockImageCloser)(nil).ConfigBlob), arg0) 71 | } 72 | 73 | // ConfigInfo mocks base method 74 | func (m *MockImageCloser) ConfigInfo() types.BlobInfo { 75 | m.ctrl.T.Helper() 76 | ret := m.ctrl.Call(m, "ConfigInfo") 77 | ret0, _ := ret[0].(types.BlobInfo) 78 | return ret0 79 | } 80 | 81 | // ConfigInfo indicates an expected call of ConfigInfo 82 | func (mr *MockImageCloserMockRecorder) ConfigInfo() *gomock.Call { 83 | mr.mock.ctrl.T.Helper() 84 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigInfo", reflect.TypeOf((*MockImageCloser)(nil).ConfigInfo)) 85 | } 86 | 87 | // EmbeddedDockerReferenceConflicts mocks base method 88 | func (m *MockImageCloser) EmbeddedDockerReferenceConflicts(arg0 reference.Named) bool { 89 | m.ctrl.T.Helper() 90 | ret := m.ctrl.Call(m, "EmbeddedDockerReferenceConflicts", arg0) 91 | ret0, _ := ret[0].(bool) 92 | return ret0 93 | } 94 | 95 | // EmbeddedDockerReferenceConflicts indicates an expected call of EmbeddedDockerReferenceConflicts 96 | func (mr *MockImageCloserMockRecorder) EmbeddedDockerReferenceConflicts(arg0 interface{}) *gomock.Call { 97 | mr.mock.ctrl.T.Helper() 98 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EmbeddedDockerReferenceConflicts", reflect.TypeOf((*MockImageCloser)(nil).EmbeddedDockerReferenceConflicts), arg0) 99 | } 100 | 101 | // Inspect mocks base method 102 | func (m *MockImageCloser) Inspect(arg0 context.Context) (*types.ImageInspectInfo, error) { 103 | m.ctrl.T.Helper() 104 | ret := m.ctrl.Call(m, "Inspect", arg0) 105 | ret0, _ := ret[0].(*types.ImageInspectInfo) 106 | ret1, _ := ret[1].(error) 107 | return ret0, ret1 108 | } 109 | 110 | // Inspect indicates an expected call of Inspect 111 | func (mr *MockImageCloserMockRecorder) Inspect(arg0 interface{}) *gomock.Call { 112 | mr.mock.ctrl.T.Helper() 113 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Inspect", reflect.TypeOf((*MockImageCloser)(nil).Inspect), arg0) 114 | } 115 | 116 | // LayerInfos mocks base method 117 | func (m *MockImageCloser) LayerInfos() []types.BlobInfo { 118 | m.ctrl.T.Helper() 119 | ret := m.ctrl.Call(m, "LayerInfos") 120 | ret0, _ := ret[0].([]types.BlobInfo) 121 | return ret0 122 | } 123 | 124 | // LayerInfos indicates an expected call of LayerInfos 125 | func (mr *MockImageCloserMockRecorder) LayerInfos() *gomock.Call { 126 | mr.mock.ctrl.T.Helper() 127 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LayerInfos", reflect.TypeOf((*MockImageCloser)(nil).LayerInfos)) 128 | } 129 | 130 | // LayerInfosForCopy mocks base method 131 | func (m *MockImageCloser) LayerInfosForCopy(arg0 context.Context) ([]types.BlobInfo, error) { 132 | m.ctrl.T.Helper() 133 | ret := m.ctrl.Call(m, "LayerInfosForCopy", arg0) 134 | ret0, _ := ret[0].([]types.BlobInfo) 135 | ret1, _ := ret[1].(error) 136 | return ret0, ret1 137 | } 138 | 139 | // LayerInfosForCopy indicates an expected call of LayerInfosForCopy 140 | func (mr *MockImageCloserMockRecorder) LayerInfosForCopy(arg0 interface{}) *gomock.Call { 141 | mr.mock.ctrl.T.Helper() 142 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LayerInfosForCopy", reflect.TypeOf((*MockImageCloser)(nil).LayerInfosForCopy), arg0) 143 | } 144 | 145 | // Manifest mocks base method 146 | func (m *MockImageCloser) Manifest(arg0 context.Context) ([]byte, string, error) { 147 | m.ctrl.T.Helper() 148 | ret := m.ctrl.Call(m, "Manifest", arg0) 149 | ret0, _ := ret[0].([]byte) 150 | ret1, _ := ret[1].(string) 151 | ret2, _ := ret[2].(error) 152 | return ret0, ret1, ret2 153 | } 154 | 155 | // Manifest indicates an expected call of Manifest 156 | func (mr *MockImageCloserMockRecorder) Manifest(arg0 interface{}) *gomock.Call { 157 | mr.mock.ctrl.T.Helper() 158 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Manifest", reflect.TypeOf((*MockImageCloser)(nil).Manifest), arg0) 159 | } 160 | 161 | // OCIConfig mocks base method 162 | func (m *MockImageCloser) OCIConfig(arg0 context.Context) (*v1.Image, error) { 163 | m.ctrl.T.Helper() 164 | ret := m.ctrl.Call(m, "OCIConfig", arg0) 165 | ret0, _ := ret[0].(*v1.Image) 166 | ret1, _ := ret[1].(error) 167 | return ret0, ret1 168 | } 169 | 170 | // OCIConfig indicates an expected call of OCIConfig 171 | func (mr *MockImageCloserMockRecorder) OCIConfig(arg0 interface{}) *gomock.Call { 172 | mr.mock.ctrl.T.Helper() 173 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OCIConfig", reflect.TypeOf((*MockImageCloser)(nil).OCIConfig), arg0) 174 | } 175 | 176 | // Reference mocks base method 177 | func (m *MockImageCloser) Reference() types.ImageReference { 178 | m.ctrl.T.Helper() 179 | ret := m.ctrl.Call(m, "Reference") 180 | ret0, _ := ret[0].(types.ImageReference) 181 | return ret0 182 | } 183 | 184 | // Reference indicates an expected call of Reference 185 | func (mr *MockImageCloserMockRecorder) Reference() *gomock.Call { 186 | mr.mock.ctrl.T.Helper() 187 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reference", reflect.TypeOf((*MockImageCloser)(nil).Reference)) 188 | } 189 | 190 | // Signatures mocks base method 191 | func (m *MockImageCloser) Signatures(arg0 context.Context) ([][]byte, error) { 192 | m.ctrl.T.Helper() 193 | ret := m.ctrl.Call(m, "Signatures", arg0) 194 | ret0, _ := ret[0].([][]byte) 195 | ret1, _ := ret[1].(error) 196 | return ret0, ret1 197 | } 198 | 199 | // Signatures indicates an expected call of Signatures 200 | func (mr *MockImageCloserMockRecorder) Signatures(arg0 interface{}) *gomock.Call { 201 | mr.mock.ctrl.T.Helper() 202 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Signatures", reflect.TypeOf((*MockImageCloser)(nil).Signatures), arg0) 203 | } 204 | 205 | // Size mocks base method 206 | func (m *MockImageCloser) Size() (int64, error) { 207 | m.ctrl.T.Helper() 208 | ret := m.ctrl.Call(m, "Size") 209 | ret0, _ := ret[0].(int64) 210 | ret1, _ := ret[1].(error) 211 | return ret0, ret1 212 | } 213 | 214 | // Size indicates an expected call of Size 215 | func (mr *MockImageCloserMockRecorder) Size() *gomock.Call { 216 | mr.mock.ctrl.T.Helper() 217 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Size", reflect.TypeOf((*MockImageCloser)(nil).Size)) 218 | } 219 | 220 | // SupportsEncryption mocks base method 221 | func (m *MockImageCloser) SupportsEncryption(arg0 context.Context) bool { 222 | m.ctrl.T.Helper() 223 | ret := m.ctrl.Call(m, "SupportsEncryption", arg0) 224 | ret0, _ := ret[0].(bool) 225 | return ret0 226 | } 227 | 228 | // SupportsEncryption indicates an expected call of SupportsEncryption 229 | func (mr *MockImageCloserMockRecorder) SupportsEncryption(arg0 interface{}) *gomock.Call { 230 | mr.mock.ctrl.T.Helper() 231 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SupportsEncryption", reflect.TypeOf((*MockImageCloser)(nil).SupportsEncryption), arg0) 232 | } 233 | 234 | // UpdatedImage mocks base method 235 | func (m *MockImageCloser) UpdatedImage(arg0 context.Context, arg1 types.ManifestUpdateOptions) (types.Image, error) { 236 | m.ctrl.T.Helper() 237 | ret := m.ctrl.Call(m, "UpdatedImage", arg0, arg1) 238 | ret0, _ := ret[0].(types.Image) 239 | ret1, _ := ret[1].(error) 240 | return ret0, ret1 241 | } 242 | 243 | // UpdatedImage indicates an expected call of UpdatedImage 244 | func (mr *MockImageCloserMockRecorder) UpdatedImage(arg0, arg1 interface{}) *gomock.Call { 245 | mr.mock.ctrl.T.Helper() 246 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatedImage", reflect.TypeOf((*MockImageCloser)(nil).UpdatedImage), arg0, arg1) 247 | } 248 | 249 | // UpdatedImageNeedsLayerDiffIDs mocks base method 250 | func (m *MockImageCloser) UpdatedImageNeedsLayerDiffIDs(arg0 types.ManifestUpdateOptions) bool { 251 | m.ctrl.T.Helper() 252 | ret := m.ctrl.Call(m, "UpdatedImageNeedsLayerDiffIDs", arg0) 253 | ret0, _ := ret[0].(bool) 254 | return ret0 255 | } 256 | 257 | // UpdatedImageNeedsLayerDiffIDs indicates an expected call of UpdatedImageNeedsLayerDiffIDs 258 | func (mr *MockImageCloserMockRecorder) UpdatedImageNeedsLayerDiffIDs(arg0 interface{}) *gomock.Call { 259 | mr.mock.ctrl.T.Helper() 260 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatedImageNeedsLayerDiffIDs", reflect.TypeOf((*MockImageCloser)(nil).UpdatedImageNeedsLayerDiffIDs), arg0) 261 | } 262 | 263 | // MockImageSource is a mock of ImageSource interface 264 | type MockImageSource struct { 265 | ctrl *gomock.Controller 266 | recorder *MockImageSourceMockRecorder 267 | } 268 | 269 | // MockImageSourceMockRecorder is the mock recorder for MockImageSource 270 | type MockImageSourceMockRecorder struct { 271 | mock *MockImageSource 272 | } 273 | 274 | // NewMockImageSource creates a new mock instance 275 | func NewMockImageSource(ctrl *gomock.Controller) *MockImageSource { 276 | mock := &MockImageSource{ctrl: ctrl} 277 | mock.recorder = &MockImageSourceMockRecorder{mock} 278 | return mock 279 | } 280 | 281 | // EXPECT returns an object that allows the caller to indicate expected use 282 | func (m *MockImageSource) EXPECT() *MockImageSourceMockRecorder { 283 | return m.recorder 284 | } 285 | 286 | // Close mocks base method 287 | func (m *MockImageSource) Close() error { 288 | m.ctrl.T.Helper() 289 | ret := m.ctrl.Call(m, "Close") 290 | ret0, _ := ret[0].(error) 291 | return ret0 292 | } 293 | 294 | // Close indicates an expected call of Close 295 | func (mr *MockImageSourceMockRecorder) Close() *gomock.Call { 296 | mr.mock.ctrl.T.Helper() 297 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockImageSource)(nil).Close)) 298 | } 299 | 300 | // GetBlob mocks base method 301 | func (m *MockImageSource) GetBlob(arg0 context.Context, arg1 types.BlobInfo, arg2 types.BlobInfoCache) (io.ReadCloser, int64, error) { 302 | m.ctrl.T.Helper() 303 | ret := m.ctrl.Call(m, "GetBlob", arg0, arg1, arg2) 304 | ret0, _ := ret[0].(io.ReadCloser) 305 | ret1, _ := ret[1].(int64) 306 | ret2, _ := ret[2].(error) 307 | return ret0, ret1, ret2 308 | } 309 | 310 | // GetBlob indicates an expected call of GetBlob 311 | func (mr *MockImageSourceMockRecorder) GetBlob(arg0, arg1, arg2 interface{}) *gomock.Call { 312 | mr.mock.ctrl.T.Helper() 313 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlob", reflect.TypeOf((*MockImageSource)(nil).GetBlob), arg0, arg1, arg2) 314 | } 315 | 316 | // GetManifest mocks base method 317 | func (m *MockImageSource) GetManifest(arg0 context.Context, arg1 *go_digest.Digest) ([]byte, string, error) { 318 | m.ctrl.T.Helper() 319 | ret := m.ctrl.Call(m, "GetManifest", arg0, arg1) 320 | ret0, _ := ret[0].([]byte) 321 | ret1, _ := ret[1].(string) 322 | ret2, _ := ret[2].(error) 323 | return ret0, ret1, ret2 324 | } 325 | 326 | // GetManifest indicates an expected call of GetManifest 327 | func (mr *MockImageSourceMockRecorder) GetManifest(arg0, arg1 interface{}) *gomock.Call { 328 | mr.mock.ctrl.T.Helper() 329 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetManifest", reflect.TypeOf((*MockImageSource)(nil).GetManifest), arg0, arg1) 330 | } 331 | 332 | // GetSignatures mocks base method 333 | func (m *MockImageSource) GetSignatures(arg0 context.Context, arg1 *go_digest.Digest) ([][]byte, error) { 334 | m.ctrl.T.Helper() 335 | ret := m.ctrl.Call(m, "GetSignatures", arg0, arg1) 336 | ret0, _ := ret[0].([][]byte) 337 | ret1, _ := ret[1].(error) 338 | return ret0, ret1 339 | } 340 | 341 | // GetSignatures indicates an expected call of GetSignatures 342 | func (mr *MockImageSourceMockRecorder) GetSignatures(arg0, arg1 interface{}) *gomock.Call { 343 | mr.mock.ctrl.T.Helper() 344 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSignatures", reflect.TypeOf((*MockImageSource)(nil).GetSignatures), arg0, arg1) 345 | } 346 | 347 | // HasThreadSafeGetBlob mocks base method 348 | func (m *MockImageSource) HasThreadSafeGetBlob() bool { 349 | m.ctrl.T.Helper() 350 | ret := m.ctrl.Call(m, "HasThreadSafeGetBlob") 351 | ret0, _ := ret[0].(bool) 352 | return ret0 353 | } 354 | 355 | // HasThreadSafeGetBlob indicates an expected call of HasThreadSafeGetBlob 356 | func (mr *MockImageSourceMockRecorder) HasThreadSafeGetBlob() *gomock.Call { 357 | mr.mock.ctrl.T.Helper() 358 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasThreadSafeGetBlob", reflect.TypeOf((*MockImageSource)(nil).HasThreadSafeGetBlob)) 359 | } 360 | 361 | // LayerInfosForCopy mocks base method 362 | func (m *MockImageSource) LayerInfosForCopy(arg0 context.Context, arg1 *go_digest.Digest) ([]types.BlobInfo, error) { 363 | m.ctrl.T.Helper() 364 | ret := m.ctrl.Call(m, "LayerInfosForCopy", arg0, arg1) 365 | ret0, _ := ret[0].([]types.BlobInfo) 366 | ret1, _ := ret[1].(error) 367 | return ret0, ret1 368 | } 369 | 370 | // LayerInfosForCopy indicates an expected call of LayerInfosForCopy 371 | func (mr *MockImageSourceMockRecorder) LayerInfosForCopy(arg0, arg1 interface{}) *gomock.Call { 372 | mr.mock.ctrl.T.Helper() 373 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LayerInfosForCopy", reflect.TypeOf((*MockImageSource)(nil).LayerInfosForCopy), arg0, arg1) 374 | } 375 | 376 | // Reference mocks base method 377 | func (m *MockImageSource) Reference() types.ImageReference { 378 | m.ctrl.T.Helper() 379 | ret := m.ctrl.Call(m, "Reference") 380 | ret0, _ := ret[0].(types.ImageReference) 381 | return ret0 382 | } 383 | 384 | // Reference indicates an expected call of Reference 385 | func (mr *MockImageSourceMockRecorder) Reference() *gomock.Call { 386 | mr.mock.ctrl.T.Helper() 387 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reference", reflect.TypeOf((*MockImageSource)(nil).Reference)) 388 | } 389 | -------------------------------------------------------------------------------- /img2lambda/publish/publish_layers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | package publish 4 | 5 | import ( 6 | "crypto/sha256" 7 | "encoding/base64" 8 | "encoding/json" 9 | "io/ioutil" 10 | "log" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | 15 | yaml "gopkg.in/yaml.v2" 16 | 17 | "github.com/aws/aws-sdk-go/aws" 18 | "github.com/aws/aws-sdk-go/service/lambda" 19 | "github.com/aws/aws-sdk-go/service/lambda/lambdaiface" 20 | "github.com/awslabs/aws-lambda-container-image-converter/img2lambda/types" 21 | ) 22 | 23 | func PublishLambdaLayers(opts *types.PublishOptions, layers []types.LambdaLayer) (string, string, error) { 24 | layerArns := []string{} 25 | 26 | for _, layer := range layers { 27 | layerName := opts.LayerPrefix + "-" + strings.Replace(layer.Digest, ":", "-", -1) 28 | 29 | var layerDescription, licenseInfo *string 30 | 31 | if opts.Description == "" { 32 | // if no description is passed from commandline, use the default description 33 | layerDescription = aws.String("created by img2lambda from image " + opts.SourceImageName) 34 | } else { 35 | layerDescription = aws.String(opts.Description) 36 | } 37 | 38 | if opts.LicenseInfo != "" { 39 | licenseInfo = aws.String(opts.LicenseInfo) 40 | } 41 | 42 | if len(opts.CompatibleRuntimes) == 0 { 43 | opts.CompatibleRuntimes = append(opts.CompatibleRuntimes, "provided") 44 | } 45 | 46 | layerContents, err := ioutil.ReadFile(layer.File) 47 | if err != nil { 48 | return "", "", err 49 | } 50 | 51 | found, existingArn, err := matchExistingLambdaLayer(layerName, layerContents, &opts.LambdaClient) 52 | if err != nil { 53 | return "", "", err 54 | } 55 | 56 | if found { 57 | layerArns = append(layerArns, existingArn) 58 | log.Printf("Matched Lambda layer file %s (image layer %s) to existing Lambda layer: %s", layer.File, layer.Digest, existingArn) 59 | } else { 60 | publishArgs := &lambda.PublishLayerVersionInput{ 61 | CompatibleRuntimes: aws.StringSlice(opts.CompatibleRuntimes), 62 | Content: &lambda.LayerVersionContentInput{ZipFile: layerContents}, 63 | Description: layerDescription, 64 | LayerName: aws.String(layerName), 65 | LicenseInfo: licenseInfo, 66 | } 67 | 68 | resp, err := opts.LambdaClient.PublishLayerVersion(publishArgs) 69 | if err != nil { 70 | return "", "", err 71 | } 72 | 73 | layerArns = append(layerArns, *resp.LayerVersionArn) 74 | log.Printf("Published Lambda layer file %s (image layer %s) to Lambda: %s", layer.File, layer.Digest, *resp.LayerVersionArn) 75 | } 76 | 77 | err = os.Remove(layer.File) 78 | if err != nil { 79 | return "", "", err 80 | } 81 | } 82 | 83 | jsonArns, err := json.MarshalIndent(layerArns, "", " ") 84 | if err != nil { 85 | return "", "", err 86 | } 87 | 88 | jsonResultsPath := filepath.Join(opts.ResultsDir, "layers.json") 89 | jsonFile, err := os.Create(jsonResultsPath) 90 | if err != nil { 91 | return "", "", err 92 | } 93 | defer jsonFile.Close() 94 | 95 | _, err = jsonFile.Write(jsonArns) 96 | if err != nil { 97 | return "", "", err 98 | } 99 | 100 | yamlArns, err := yaml.Marshal(layerArns) 101 | if err != nil { 102 | return "", "", err 103 | } 104 | 105 | yamlResultsPath := filepath.Join(opts.ResultsDir, "layers.yaml") 106 | yamlFile, err := os.Create(yamlResultsPath) 107 | if err != nil { 108 | return "", "", err 109 | } 110 | defer yamlFile.Close() 111 | 112 | _, err = yamlFile.Write(yamlArns) 113 | if err != nil { 114 | return "", "", err 115 | } 116 | 117 | log.Printf("Lambda layer ARNs (%d total) are written to %s and %s", len(layerArns), jsonResultsPath, yamlResultsPath) 118 | 119 | return jsonResultsPath, yamlResultsPath, nil 120 | } 121 | 122 | func matchExistingLambdaLayer(layerName string, layerContents []byte, lambdaClient *lambdaiface.LambdaAPI) (bool, string, error) { 123 | hash := sha256.Sum256(layerContents) 124 | hashStr := base64.StdEncoding.EncodeToString(hash[:]) 125 | 126 | var marker *string 127 | client := *lambdaClient 128 | 129 | for { 130 | listArgs := &lambda.ListLayerVersionsInput{ 131 | LayerName: aws.String(layerName), 132 | Marker: marker, 133 | } 134 | 135 | resp, err := client.ListLayerVersions(listArgs) 136 | if err != nil { 137 | return false, "", err 138 | } 139 | 140 | for _, layerVersion := range resp.LayerVersions { 141 | getArgs := &lambda.GetLayerVersionInput{ 142 | LayerName: aws.String(layerName), 143 | VersionNumber: layerVersion.Version, 144 | } 145 | 146 | layerResp, err := client.GetLayerVersion(getArgs) 147 | if err != nil { 148 | return false, "", err 149 | } 150 | 151 | if *layerResp.Content.CodeSha256 == hashStr && *layerResp.Content.CodeSize == int64(len(layerContents)) { 152 | return true, *layerResp.LayerVersionArn, nil 153 | } 154 | } 155 | 156 | if resp.NextMarker == nil { 157 | break 158 | } 159 | 160 | marker = resp.NextMarker 161 | } 162 | 163 | return false, "", nil 164 | } 165 | -------------------------------------------------------------------------------- /img2lambda/publish/publish_layers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | package publish 4 | 5 | import ( 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io/ioutil" 10 | "os" 11 | "strconv" 12 | "testing" 13 | 14 | yaml "gopkg.in/yaml.v2" 15 | 16 | "github.com/aws/aws-sdk-go/aws" 17 | "github.com/aws/aws-sdk-go/service/lambda" 18 | "github.com/awslabs/aws-lambda-container-image-converter/img2lambda/internal/testing/mocks" 19 | "github.com/awslabs/aws-lambda-container-image-converter/img2lambda/types" 20 | "github.com/golang/mock/gomock" 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func parseJSONResult(t *testing.T, resultsFilename string) []string { 25 | resultContents, err := ioutil.ReadFile(resultsFilename) 26 | assert.Nil(t, err) 27 | var resultArns []string 28 | err = json.Unmarshal(resultContents, &resultArns) 29 | assert.Nil(t, err) 30 | os.Remove(resultsFilename) 31 | return resultArns 32 | } 33 | 34 | func parseYAMLResult(t *testing.T, resultsFilename string) []string { 35 | resultContents, err := ioutil.ReadFile(resultsFilename) 36 | assert.Nil(t, err) 37 | var resultArns []string 38 | err = yaml.Unmarshal(resultContents, &resultArns) 39 | assert.Nil(t, err) 40 | os.Remove(resultsFilename) 41 | return resultArns 42 | } 43 | 44 | func mockLayer(t *testing.T, n int) types.LambdaLayer { 45 | tmpFile, err := ioutil.TempFile("", "") 46 | assert.Nil(t, err) 47 | defer tmpFile.Close() 48 | 49 | _, err = tmpFile.WriteString("hello world " + strconv.Itoa(n)) 50 | assert.Nil(t, err) 51 | 52 | return types.LambdaLayer{ 53 | Digest: "sha256:" + strconv.Itoa(n), 54 | File: tmpFile.Name(), 55 | } 56 | } 57 | 58 | func mockLayers(t *testing.T) []types.LambdaLayer { 59 | var layers []types.LambdaLayer 60 | 61 | layers = append(layers, mockLayer(t, 1)) 62 | layers = append(layers, mockLayer(t, 2)) 63 | layers = append(layers, mockLayer(t, 3)) 64 | 65 | return layers 66 | } 67 | 68 | func mockPublishNoExistingLayers(t *testing.T, lambdaClient *mocks.MockLambdaAPI, n int) { 69 | layerName := aws.String(fmt.Sprintf("test-prefix-sha256-%d", n)) 70 | 71 | expectedPublishInput := &lambda.PublishLayerVersionInput{ 72 | CompatibleRuntimes: []*string{aws.String("provided")}, 73 | Content: &lambda.LayerVersionContentInput{ZipFile: []byte(fmt.Sprintf("hello world %d", n))}, 74 | Description: aws.String("created by img2lambda from image test-image"), 75 | LayerName: layerName, 76 | } 77 | 78 | expectedPublishOutput := &lambda.PublishLayerVersionOutput{ 79 | LayerVersionArn: aws.String(fmt.Sprintf("arn:aws:lambda:us-east-2:123456789012:layer:example-layer-%d:1", n)), 80 | } 81 | 82 | // Test out pagination 83 | expectedListInput1 := &lambda.ListLayerVersionsInput{ 84 | LayerName: layerName, 85 | } 86 | 87 | expectedListOutput1 := &lambda.ListLayerVersionsOutput{ 88 | LayerVersions: []*lambda.LayerVersionsListItem{}, 89 | NextMarker: aws.String("hello"), 90 | } 91 | 92 | expectedListInput2 := &lambda.ListLayerVersionsInput{ 93 | LayerName: layerName, 94 | Marker: aws.String("hello"), 95 | } 96 | 97 | expectedListOutput2 := &lambda.ListLayerVersionsOutput{ 98 | LayerVersions: []*lambda.LayerVersionsListItem{}, 99 | } 100 | 101 | gomock.InOrder( 102 | lambdaClient.EXPECT().ListLayerVersions(gomock.Eq(expectedListInput1)).Return(expectedListOutput1, nil), 103 | lambdaClient.EXPECT().ListLayerVersions(gomock.Eq(expectedListInput2)).Return(expectedListOutput2, nil), 104 | lambdaClient.EXPECT().PublishLayerVersion(gomock.Eq(expectedPublishInput)).Return(expectedPublishOutput, nil), 105 | ) 106 | } 107 | 108 | func mockPublishNoMatchingLayers(t *testing.T, lambdaClient *mocks.MockLambdaAPI, n int) { 109 | layerName := aws.String(fmt.Sprintf("test-prefix-sha256-%d", n)) 110 | 111 | expectedPublishInput := &lambda.PublishLayerVersionInput{ 112 | CompatibleRuntimes: []*string{aws.String("provided")}, 113 | Content: &lambda.LayerVersionContentInput{ZipFile: []byte(fmt.Sprintf("hello world %d", n))}, 114 | Description: aws.String("created by img2lambda from image test-image"), 115 | LayerName: layerName, 116 | } 117 | 118 | expectedPublishOutput := &lambda.PublishLayerVersionOutput{ 119 | LayerVersionArn: aws.String(fmt.Sprintf("arn:aws:lambda:us-east-2:123456789012:layer:example-layer-%d:1", n)), 120 | } 121 | 122 | expectedListInput := &lambda.ListLayerVersionsInput{ 123 | LayerName: layerName, 124 | } 125 | 126 | var existingVersions []*lambda.LayerVersionsListItem 127 | existingVersionNumber := int64(0) 128 | existingVersionListItem := &lambda.LayerVersionsListItem{ 129 | LayerVersionArn: aws.String(fmt.Sprintf("arn:aws:lambda:us-east-2:123456789012:layer:example-layer-%d:0", n)), 130 | Version: &existingVersionNumber, 131 | } 132 | existingVersions = append(existingVersions, existingVersionListItem) 133 | expectedListOutput := &lambda.ListLayerVersionsOutput{ 134 | LayerVersions: existingVersions, 135 | } 136 | 137 | expectedGetInput := &lambda.GetLayerVersionInput{ 138 | LayerName: layerName, 139 | VersionNumber: &existingVersionNumber, 140 | } 141 | 142 | size := int64(0) 143 | expectedContentOutput := &lambda.LayerVersionContentOutput{ 144 | CodeSha256: aws.String("kjsdflkjfd"), 145 | CodeSize: &size, 146 | } 147 | 148 | expectedGetOutput := &lambda.GetLayerVersionOutput{ 149 | Version: &existingVersionNumber, 150 | Content: expectedContentOutput, 151 | LayerVersionArn: aws.String(fmt.Sprintf("arn:aws:lambda:us-east-2:123456789012:layer:example-layer-%d:0", n)), 152 | } 153 | 154 | gomock.InOrder( 155 | lambdaClient.EXPECT().ListLayerVersions(gomock.Eq(expectedListInput)).Return(expectedListOutput, nil), 156 | lambdaClient.EXPECT().GetLayerVersion(gomock.Eq(expectedGetInput)).Return(expectedGetOutput, nil), 157 | lambdaClient.EXPECT().PublishLayerVersion(gomock.Eq(expectedPublishInput)).Return(expectedPublishOutput, nil), 158 | ) 159 | } 160 | 161 | func mockMatchingLayer(t *testing.T, lambdaClient *mocks.MockLambdaAPI, n int) { 162 | layerName := aws.String(fmt.Sprintf("test-prefix-sha256-%d", n)) 163 | 164 | expectedListInput := &lambda.ListLayerVersionsInput{ 165 | LayerName: layerName, 166 | } 167 | 168 | var existingVersions []*lambda.LayerVersionsListItem 169 | existingVersionNumber := int64(0) 170 | existingVersionListItem := &lambda.LayerVersionsListItem{ 171 | LayerVersionArn: aws.String(fmt.Sprintf("arn:aws:lambda:us-east-2:123456789012:layer:example-layer-%d:1", n)), 172 | Version: &existingVersionNumber, 173 | } 174 | existingVersions = append(existingVersions, existingVersionListItem) 175 | expectedListOutput := &lambda.ListLayerVersionsOutput{ 176 | LayerVersions: existingVersions, 177 | } 178 | 179 | expectedGetInput := &lambda.GetLayerVersionInput{ 180 | LayerName: layerName, 181 | VersionNumber: &existingVersionNumber, 182 | } 183 | 184 | size := int64(13) 185 | expectedContentOutput := &lambda.LayerVersionContentOutput{ 186 | CodeSha256: aws.String("T/q7q052MgJGLfH1mBGUQSFYjwVn9VvOWBoOmevPZgY="), 187 | CodeSize: &size, 188 | } 189 | 190 | expectedGetOutput := &lambda.GetLayerVersionOutput{ 191 | Version: &existingVersionNumber, 192 | Content: expectedContentOutput, 193 | LayerVersionArn: aws.String(fmt.Sprintf("arn:aws:lambda:us-east-2:123456789012:layer:example-layer-%d:1", n)), 194 | } 195 | 196 | gomock.InOrder( 197 | lambdaClient.EXPECT().ListLayerVersions(gomock.Eq(expectedListInput)).Return(expectedListOutput, nil), 198 | lambdaClient.EXPECT().GetLayerVersion(gomock.Eq(expectedGetInput)).Return(expectedGetOutput, nil), 199 | ) 200 | } 201 | 202 | func TestNoLayers(t *testing.T) { 203 | ctrl := gomock.NewController(t) 204 | defer ctrl.Finish() 205 | 206 | lambdaClient := mocks.NewMockLambdaAPI(ctrl) 207 | 208 | dir, err := ioutil.TempDir("", "") 209 | assert.Nil(t, err) 210 | 211 | opts := &types.PublishOptions{ 212 | LambdaClient: lambdaClient, 213 | LayerPrefix: "test-prefix", 214 | SourceImageName: "test-image", 215 | ResultsDir: dir, 216 | } 217 | 218 | layers := []types.LambdaLayer{} 219 | 220 | jsonResultsFilename, yamlResultsFilename, err := PublishLambdaLayers(opts, layers) 221 | assert.Nil(t, err) 222 | 223 | resultArns := parseJSONResult(t, jsonResultsFilename) 224 | assert.Len(t, resultArns, 0) 225 | 226 | resultArns = parseYAMLResult(t, yamlResultsFilename) 227 | assert.Len(t, resultArns, 0) 228 | 229 | os.Remove(dir) 230 | } 231 | 232 | func TestPublishSuccess(t *testing.T) { 233 | ctrl := gomock.NewController(t) 234 | defer ctrl.Finish() 235 | 236 | lambdaClient := mocks.NewMockLambdaAPI(ctrl) 237 | 238 | dir, err := ioutil.TempDir("", "") 239 | assert.Nil(t, err) 240 | 241 | opts := &types.PublishOptions{ 242 | LambdaClient: lambdaClient, 243 | LayerPrefix: "test-prefix", 244 | SourceImageName: "test-image", 245 | ResultsDir: dir, 246 | } 247 | 248 | layers := mockLayers(t) 249 | 250 | mockPublishNoExistingLayers(t, lambdaClient, 1) 251 | mockPublishNoMatchingLayers(t, lambdaClient, 2) 252 | mockMatchingLayer(t, lambdaClient, 3) 253 | 254 | jsonResultsFilename, yamlResultsFilename, err := PublishLambdaLayers(opts, layers) 255 | assert.Nil(t, err) 256 | 257 | resultArns := parseJSONResult(t, jsonResultsFilename) 258 | assert.Len(t, resultArns, 3) 259 | assert.Equal(t, "arn:aws:lambda:us-east-2:123456789012:layer:example-layer-1:1", resultArns[0]) 260 | assert.Equal(t, "arn:aws:lambda:us-east-2:123456789012:layer:example-layer-2:1", resultArns[1]) 261 | assert.Equal(t, "arn:aws:lambda:us-east-2:123456789012:layer:example-layer-3:1", resultArns[2]) 262 | 263 | resultArns = parseYAMLResult(t, yamlResultsFilename) 264 | assert.Len(t, resultArns, 3) 265 | assert.Equal(t, "arn:aws:lambda:us-east-2:123456789012:layer:example-layer-1:1", resultArns[0]) 266 | assert.Equal(t, "arn:aws:lambda:us-east-2:123456789012:layer:example-layer-2:1", resultArns[1]) 267 | assert.Equal(t, "arn:aws:lambda:us-east-2:123456789012:layer:example-layer-3:1", resultArns[2]) 268 | 269 | os.Remove(dir) 270 | } 271 | 272 | func TestPublishError(t *testing.T) { 273 | ctrl := gomock.NewController(t) 274 | defer ctrl.Finish() 275 | 276 | lambdaClient := mocks.NewMockLambdaAPI(ctrl) 277 | 278 | dir, err := ioutil.TempDir("", "") 279 | assert.Nil(t, err) 280 | 281 | opts := &types.PublishOptions{ 282 | LambdaClient: lambdaClient, 283 | LayerPrefix: "test-prefix", 284 | SourceImageName: "test-image", 285 | ResultsDir: dir, 286 | } 287 | 288 | layers := mockLayers(t) 289 | 290 | expectedListInput := &lambda.ListLayerVersionsInput{ 291 | LayerName: aws.String("test-prefix-sha256-1"), 292 | } 293 | 294 | expectedListOutput := &lambda.ListLayerVersionsOutput{ 295 | LayerVersions: []*lambda.LayerVersionsListItem{}, 296 | } 297 | 298 | lambdaClient.EXPECT().ListLayerVersions(gomock.Eq(expectedListInput)).Return(expectedListOutput, nil) 299 | 300 | expectedInput1 := &lambda.PublishLayerVersionInput{ 301 | CompatibleRuntimes: []*string{aws.String("provided")}, 302 | Content: &lambda.LayerVersionContentInput{ZipFile: []byte("hello world 1")}, 303 | Description: aws.String("created by img2lambda from image test-image"), 304 | LayerName: aws.String("test-prefix-sha256-1"), 305 | } 306 | 307 | lambdaClient.EXPECT(). 308 | PublishLayerVersion(gomock.Eq(expectedInput1)). 309 | Return(nil, errors.New("Access denied")) 310 | 311 | jsonResultsFilename, yamlResultsFilename, err := PublishLambdaLayers(opts, layers) 312 | assert.Error(t, err) 313 | assert.Equal(t, "", jsonResultsFilename) 314 | assert.Equal(t, "", yamlResultsFilename) 315 | 316 | os.Remove(layers[0].File) 317 | os.Remove(layers[1].File) 318 | os.Remove(layers[2].File) 319 | 320 | os.Remove(dir) 321 | } 322 | -------------------------------------------------------------------------------- /img2lambda/types/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | package types 4 | 5 | import ( 6 | "github.com/aws/aws-sdk-go/service/lambda/lambdaiface" 7 | "github.com/awslabs/aws-lambda-container-image-converter/img2lambda/clients" 8 | ) 9 | 10 | type LambdaDeploymentPackage struct { 11 | FileCount int 12 | File string 13 | } 14 | 15 | type LambdaLayer struct { 16 | Digest string 17 | File string 18 | } 19 | 20 | type CmdOptions struct { 21 | Image string // Name of the container image 22 | ImageType string // Type of the container image 23 | Region string // AWS region 24 | Profile string // AWS credentials profile 25 | OutputDir string // Output directory for the Lambda layers 26 | DryRun bool // Dry-run (will not register with Lambda) 27 | LayerNamespace string // Prefix for published Lambda layers 28 | Description string // Description of the current layer version 29 | LicenseInfo string // Layer's software license 30 | CompatibleRuntimes []string // A list of function runtimes compatible with the current layer 31 | } 32 | 33 | type PublishOptions struct { 34 | LambdaClient lambdaiface.LambdaAPI 35 | LayerPrefix string 36 | ResultsDir string 37 | SourceImageName string 38 | Description string 39 | LicenseInfo string 40 | CompatibleRuntimes []string 41 | } 42 | 43 | func ConvertToPublishOptions(opts *CmdOptions) *PublishOptions { 44 | return &PublishOptions{ 45 | SourceImageName: opts.Image, 46 | LambdaClient: clients.NewLambdaClient(opts.Region, opts.Profile), 47 | LayerPrefix: opts.LayerNamespace, 48 | ResultsDir: opts.OutputDir, 49 | Description: opts.Description, 50 | LicenseInfo: opts.LicenseInfo, 51 | CompatibleRuntimes: opts.CompatibleRuntimes, 52 | } 53 | } 54 | 55 | // valid aws lambda function runtimes 56 | type Runtimes []string 57 | 58 | // utility function to validate if a runtime is valid (supported by aws) or not 59 | func (r Runtimes) Contains(runtime string) bool { 60 | for _, value := range r { 61 | if value == runtime { 62 | return true 63 | } 64 | } 65 | return false 66 | } 67 | 68 | // a list of aws supported runtimes as of 2020-06-18 69 | var ValidRuntimes = Runtimes{ 70 | // eol'ed runtimes are included to support existing functions 71 | "nodejs", // eol 72 | "nodejs4.3", // eol 73 | "nodejs4.3-edge", // eol 74 | "nodejs6.10", // eol 75 | "nodejs8.10", // eol 76 | "nodejs10.x", 77 | "nodejs12.x", 78 | "java8", 79 | "java11", 80 | "python2.7", 81 | "python3.6", 82 | "python3.7", 83 | "python3.8", 84 | "dotnetcore1.0", // eol 85 | "dotnetcore2.0", // eol 86 | "dotnetcore2.1", 87 | "dotnetcore3.1", 88 | "go1.x", 89 | "ruby2.5", 90 | "ruby2.7", 91 | "provided", 92 | } 93 | -------------------------------------------------------------------------------- /img2lambda/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | package version 4 | 5 | // Version indicates which version of the binary is running. 6 | var Version = "mainline" 7 | 8 | // GitCommitSHA indicates which git shorthash the binary was built off of 9 | var GitCommitSHA string 10 | 11 | func VersionString() string { 12 | return Version + " (" + GitCommitSHA + ")" 13 | } 14 | -------------------------------------------------------------------------------- /scripts/build_binary.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e 4 | 5 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 6 | # SPDX-License-Identifier: MIT-0 7 | 8 | # Builds the binary from source in the specified destination paths. 9 | mkdir -p $1 10 | 11 | PACKAGE_ROOT="github.com/awslabs/aws-lambda-container-image-converter/img2lambda" 12 | 13 | BUILDTAGS="containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_overlay exclude_graphdriver_btrfs containers_image_openpgp" 14 | 15 | VERSION_LDFLAGS="" 16 | if [ -n "${3}" ]; then 17 | VERSION_LDFLAGS="-X ${PACKAGE_ROOT}/version.Version=${3}" 18 | fi 19 | 20 | if [ -n "${4}" ]; then 21 | VERSION_LDFLAGS="$VERSION_LDFLAGS -X ${PACKAGE_ROOT}/version.GitCommitSHA=${4}" 22 | fi 23 | 24 | GOOS=$TARGET_GOOS go build -a -tags="${BUILDTAGS}" -ldflags "-s ${VERSION_LDFLAGS}" -o $1/$2 ./img2lambda/cli 25 | 26 | go test -v -tags="${BUILDTAGS}" -timeout 30s -short -cover $(go list ./img2lambda/... | grep -v /vendor/ | grep -v /internal/) 27 | 28 | cd $1 29 | 30 | if hash md5sum 2>/dev/null; then 31 | md5sum $2 > $2.md5 32 | else 33 | md5 $2 > $2.md5 34 | fi 35 | 36 | if hash sha256sum 2>/dev/null; then 37 | sha256sum $2 > $2.sha256 38 | else 39 | shasum -a 256 $2 > $2.sha256 40 | fi 41 | -------------------------------------------------------------------------------- /scripts/build_example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 6 | # SPDX-License-Identifier: MIT-0 7 | 8 | # Normalize to working directory being build root (up one level from ./scripts) 9 | ROOT=$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd ) 10 | cd "${ROOT}" 11 | 12 | cd "${ROOT}"/example 13 | 14 | # Build the example and parse the layers 15 | docker build -t lambda-php . 16 | 17 | docker run lambda-php hello '{"name": "World"}' 18 | 19 | docker run lambda-php goodbye '{"name": "World"}' 20 | 21 | ../bin/local/img2lambda -i lambda-php:latest --dry-run 22 | 23 | # Look for the layers that contain files in opt/ and deployment package that contains files in var/task/ 24 | ls output/layer-1.zip 25 | ls output/layer-2.zip 26 | ls output/function.zip 27 | 28 | unzip -l output/layer-1.zip | grep '2 files' 29 | unzip -l output/layer-1.zip | grep 'bin/php' 30 | unzip -l output/layer-1.zip | grep 'bootstrap' 31 | 32 | unzip -l output/function.zip | grep '2 files' 33 | unzip -l output/function.zip | grep 'src/hello.php' 34 | unzip -l output/function.zip | grep 'src/goodbye.php' 35 | -------------------------------------------------------------------------------- /scripts/mockgen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: MIT-0 5 | 6 | # This script wraps the mockgen tool and inserts licensing information. 7 | 8 | set -e 9 | package=${1?Must provide package} 10 | interfaces=${2?Must provide interface names} 11 | outputfile=${3?Must provide an output file} 12 | PROJECT_VENDOR="github.com/awslabs/aws-lambda-container-image-converter/img2lambda/vendor/" 13 | 14 | export PATH="${GOPATH//://bin:}/bin:$PATH" 15 | 16 | data=$( 17 | cat << EOF 18 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 19 | // SPDX-License-Identifier: MIT-0 20 | $(mockgen -package mocks "${package}" "${interfaces}") 21 | EOF 22 | ) 23 | 24 | mkdir -p $(dirname ${outputfile}) 25 | 26 | echo "$data" | sed -e "s|${PROJECT_VENDOR}||" | goimports > "${outputfile}" 27 | --------------------------------------------------------------------------------