├── .dockerignore ├── .github ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ └── feature_request.yaml ├── dependabot.yml ├── linters │ ├── .markdown-lint.yml │ └── .yamllint.yml └── workflows │ ├── bump-version.yml │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── lint.yml │ └── tagged-release.yml ├── .gitignore ├── Dockerfile ├── Dockerfile.hub ├── LICENSE ├── Makefile ├── README.md ├── builders ├── dotnet.go ├── go.go ├── handler.go ├── main.go ├── nodejs.go ├── python.go └── ruby.go ├── commands └── build.go ├── go.mod ├── go.sum ├── io └── main.go ├── main.go ├── test.bats ├── tests ├── README.md ├── dotnet6 │ ├── Function.cs │ ├── test.bats │ ├── test.csproj │ └── test.sln ├── go-nomod │ ├── main.go │ └── test.bats ├── go │ ├── go.mod │ ├── go.sum │ ├── main.go │ └── test.bats ├── hooks │ ├── bin │ │ ├── post_compile │ │ └── pre_compile │ ├── function.py │ ├── requirements.txt │ └── test.bats ├── lambda.yml-invalid-image │ ├── Function.cs │ ├── lambda.yml │ ├── test.csproj │ └── test.sln ├── lambda.yml-nonexistent-builder │ ├── Function.cs │ ├── lambda.yml │ ├── test.csproj │ └── test.sln ├── lambda.yml │ ├── Function.cs │ ├── lambda.yml │ ├── test.bats │ ├── test.csproj │ └── test.sln ├── not-detected │ └── function.py ├── npm │ ├── function.js │ ├── package-lock.json │ ├── package.json │ └── test.bats ├── pip-runtime │ ├── function.py │ ├── requirements.txt │ ├── runtime.txt │ └── test.bats ├── pip │ ├── function.py │ ├── requirements.txt │ └── test.bats ├── pipenv │ ├── Pipfile │ ├── Pipfile.lock │ ├── function.py │ └── test.bats ├── poetry │ ├── function.py │ ├── poetry.lock │ ├── pyproject.toml │ └── test.bats └── ruby │ ├── Gemfile │ ├── Gemfile.lock │ ├── function.rb │ └── test.bats └── ui ├── human_writer.go └── zerolog_ui.go /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile* 2 | LICENSE 3 | README.md 4 | -------------------------------------------------------------------------------- /.github/.ruby-version: -------------------------------------------------------------------------------- 1 | 3.3.1 2 | -------------------------------------------------------------------------------- /.github/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | ruby file: ".ruby-version" 4 | 5 | gem "fpm" 6 | gem "package_cloud" 7 | -------------------------------------------------------------------------------- /.github/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | arr-pm (0.0.12) 5 | backports (3.25.0) 6 | cabin (0.9.0) 7 | clamp (1.3.2) 8 | domain_name (0.6.20240107) 9 | dotenv (3.1.4) 10 | fpm (1.16.0) 11 | arr-pm (~> 0.0.11) 12 | backports (>= 2.6.2) 13 | cabin (>= 0.6.0) 14 | clamp (>= 1.0.0) 15 | pleaserun (~> 0.0.29) 16 | rexml 17 | stud 18 | highline (2.0.3) 19 | http-accept (1.7.0) 20 | http-cookie (1.0.5) 21 | domain_name (~> 0.5) 22 | insist (1.0.0) 23 | json_pure (2.3.1) 24 | mime-types (3.5.2) 25 | mime-types-data (~> 3.2015) 26 | mime-types-data (3.2024.0507) 27 | mustache (0.99.8) 28 | netrc (0.11.0) 29 | package_cloud (0.3.14) 30 | highline (~> 2.0.0) 31 | json_pure (~> 2.3.0) 32 | rainbow (= 2.2.2) 33 | rest-client (~> 2.0) 34 | thor (~> 1.2) 35 | pleaserun (0.0.32) 36 | cabin (> 0) 37 | clamp 38 | dotenv 39 | insist 40 | mustache (= 0.99.8) 41 | stud 42 | rainbow (2.2.2) 43 | rake 44 | rake (13.2.1) 45 | rest-client (2.1.0) 46 | http-accept (>= 1.7.0, < 2.0) 47 | http-cookie (>= 1.0.2, < 2.0) 48 | mime-types (>= 1.16, < 4.0) 49 | netrc (~> 0.8) 50 | rexml (3.3.9) 51 | stud (0.0.23) 52 | thor (1.3.1) 53 | 54 | PLATFORMS 55 | arm64-darwin-23 56 | ruby 57 | 58 | DEPENDENCIES 59 | fpm 60 | package_cloud 61 | 62 | RUBY VERSION 63 | ruby 3.3.1p55 64 | 65 | BUNDLED WITH 66 | 2.5.9 67 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | description: File a bug report 4 | labels: ["needs: investigation", "type: bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Consider [sponsoring Dokku](https://github.com/sponsors/dokku). Sponsorship goes directly to supporting activities such as fixing bugs and general maintenance. 10 | - type: textarea 11 | id: description 12 | attributes: 13 | label: Description of problem 14 | description: What happened? What did you expect to happen? 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: steps 19 | attributes: 20 | label: Steps to reproduce 21 | description: What are the steps that we need to follow to reproduce this issue? 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: report-output 26 | attributes: 27 | label: lambda-builder version 28 | description: Please paste the output of the command `lambda-builder version` 29 | validations: 30 | required: true 31 | - type: textarea 32 | id: logs 33 | attributes: 34 | label: "Output of failing command" 35 | render: shell 36 | validations: 37 | required: false 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | description: Have an idea for a new feature? Ask away! 4 | labels: ["needs: plan", "type: enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | While pull requests are always welcome, feel free to fill this out with an idea of what you would like to happen and we can discuss if and how it should be implemented in `lambda-builder`. 10 | 11 | Consider [sponsoring Dokku](https://github.com/sponsors/dokku). Sponsorship goes directly to supporting activities such as implementing new features and upgrading existing functionality. 12 | - type: textarea 13 | id: description 14 | attributes: 15 | label: Description of feature 16 | description: When you perform what command or action, what do you think should happen? 17 | validations: 18 | required: true 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: "bundler" 5 | directory: "/.github" 6 | schedule: 7 | interval: "daily" 8 | - package-ecosystem: "docker" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "gomod" 13 | directory: "/" 14 | schedule: 15 | interval: "daily" 16 | - package-ecosystem: "github-actions" 17 | directory: "/" 18 | schedule: 19 | interval: "daily" 20 | -------------------------------------------------------------------------------- /.github/linters/.markdown-lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | default: true 3 | 4 | # Line length 5 | # https://github.com/DavidAnson/markdownlint/blob/master/doc/Rules.md#md013 6 | MD013: false 7 | -------------------------------------------------------------------------------- /.github/linters/.yamllint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | line-length: disable 6 | -------------------------------------------------------------------------------- /.github/workflows/bump-version.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "bump-version" 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | bump_type: 9 | description: "Bump type" 10 | default: "patch" 11 | required: true 12 | type: choice 13 | options: 14 | - patch 15 | - minor 16 | - major 17 | 18 | env: 19 | GITHUB_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} 20 | 21 | jobs: 22 | bump-version: 23 | name: bump-version 24 | runs-on: ubuntu-24.04 25 | 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v4.1.4 29 | with: 30 | fetch-depth: 0 31 | token: ${{ env.GITHUB_ACCESS_TOKEN }} 32 | 33 | - name: Get Latest Tag 34 | id: latest-tag 35 | run: | 36 | echo GIT_LATEST_TAG="$(git describe --tags "$(git rev-list --tags --max-count=1)")" >>"$GITHUB_OUTPUT" 37 | 38 | - name: Compute Next Tag 39 | id: next-tag 40 | uses: docker://ghcr.io/dokku/semver-generator:latest 41 | with: 42 | bump: ${{ github.event.inputs.bump_type }} 43 | input: ${{ steps.latest-tag.outputs.GIT_LATEST_TAG }} 44 | 45 | - name: Create and Push Tag 46 | run: | 47 | git config --global user.name 'Dokku Bot' 48 | git config --global user.email no-reply@dokku.com 49 | git tag "$GIT_NEXT_TAG" 50 | git push origin "$GIT_NEXT_TAG" 51 | env: 52 | GIT_NEXT_TAG: ${{ steps.next-tag.outputs.version }} 53 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | pull_request: 7 | branches: 8 | - "*" 9 | push: 10 | branches: 11 | - "main" 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | build: 19 | name: build 20 | runs-on: ubuntu-24.04 21 | env: 22 | GITHUB_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} 23 | PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }} 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: make version 28 | run: | 29 | make version .env.docker 30 | - run: make ci-report 31 | - run: make build-docker-image 32 | - run: make build-in-docker 33 | - run: make validate-in-docker 34 | - name: upload packages 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: build 38 | path: build/**/* 39 | 40 | binary-check: 41 | name: binary-check 42 | runs-on: ubuntu-24.04 43 | needs: build 44 | steps: 45 | - uses: actions/checkout@v4 46 | - name: Get Repository Name 47 | id: repo-name 48 | run: | 49 | echo "REPOSITORY_NAME=$(echo "${{ github.repository }}" | cut -d '/' -f 2)" >> $GITHUB_OUTPUT 50 | echo "TARGET_ARCHITECTURE=$(dpkg --print-architecture)" >> $GITHUB_OUTPUT 51 | echo "GO_VERSION=$(go mod edit -json | jq -r .Go)" >> $GITHUB_OUTPUT 52 | - name: Build binaries 53 | uses: crazy-max/ghaction-xgo@v3 54 | with: 55 | xgo_version: latest 56 | go_version: "${{ steps.repo-name.outputs.GO_VERSION }}" 57 | dest: dist 58 | prefix: ${{ steps.repo-name.outputs.REPOSITORY_NAME }} 59 | targets: linux/${{ steps.repo-name.outputs.TARGET_ARCHITECTURE }} 60 | v: true 61 | x: false 62 | race: false 63 | ldflags: -s -w -X main.Version=${{ github.ref_name }} 64 | buildmode: default 65 | trimpath: true 66 | - name: Check version 67 | run: | 68 | "dist/${{ steps.repo-name.outputs.REPOSITORY_NAME }}-linux-${{ steps.repo-name.outputs.TARGET_ARCHITECTURE }}" --version 69 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "CodeQL" 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | push: 7 | branches: 8 | - "main" 9 | pull_request: 10 | branches: 11 | - "main" 12 | schedule: 13 | - cron: "31 22 * * 5" 14 | 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.ref }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | analyze: 21 | name: Analyze 22 | runs-on: ubuntu-latest 23 | permissions: 24 | actions: read 25 | contents: read 26 | security-events: write 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: ["go"] 32 | 33 | steps: 34 | - name: Checkout repository 35 | uses: actions/checkout@v4 36 | 37 | - name: Initialize CodeQL 38 | uses: github/codeql-action/init@v3 39 | with: 40 | languages: ${{ matrix.language }} 41 | 42 | - name: Autobuild 43 | uses: github/codeql-action/autobuild@v3 44 | 45 | - name: Perform CodeQL Analysis 46 | uses: github/codeql-action/analyze@v3 47 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "lint" 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | pull_request: 7 | branches: 8 | - "*" 9 | push: 10 | branches: 11 | - "main" 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | hadolint: 19 | name: hadolint 20 | runs-on: ubuntu-24.04 21 | steps: 22 | - name: Clone 23 | uses: actions/checkout@v4 24 | - name: Run hadolint 25 | uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf 26 | 27 | markdown-lint: 28 | name: markdown-lint 29 | runs-on: ubuntu-24.04 30 | steps: 31 | - name: Clone 32 | uses: actions/checkout@v4 33 | - name: Run markdown-lint 34 | uses: avto-dev/markdown-lint@04d43ee9191307b50935a753da3b775ab695eceb 35 | with: 36 | config: ".github/linters/.markdown-lint.yml" 37 | args: "./README.md" 38 | 39 | shellcheck: 40 | name: shellcheck 41 | runs-on: ubuntu-24.04 42 | steps: 43 | - name: Clone 44 | uses: actions/checkout@v4 45 | - name: Run shellcheck 46 | uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 47 | env: 48 | SHELLCHECK_OPTS: -s bash 49 | shfmt: 50 | name: shfmt 51 | runs-on: ubuntu-24.04 52 | steps: 53 | - name: Clone 54 | uses: actions/checkout@v4 55 | - name: Run shfmt 56 | uses: luizm/action-sh-checker@17bd25a6ee188d2b91f677060038f4ba37ba14b2 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} 59 | SHFMT_OPTS: -l -d -i 2 60 | with: 61 | sh_checker_shellcheck_disable: true 62 | sh_checker_comment: true 63 | 64 | yamllint: 65 | name: yamllint 66 | runs-on: ubuntu-24.04 67 | steps: 68 | - name: Clone 69 | uses: actions/checkout@v4 70 | - name: Run yamllint 71 | uses: ibiqlik/action-yamllint@2576378a8e339169678f9939646ee3ee325e845c 72 | with: 73 | config_file: ".github/linters/.yamllint.yml" 74 | -------------------------------------------------------------------------------- /.github/workflows/tagged-release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "tagged-release" 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | push: 7 | tags: 8 | - "*" 9 | 10 | permissions: 11 | attestations: write 12 | id-token: write 13 | contents: write 14 | 15 | jobs: 16 | tagged-release: 17 | name: tagged-release 18 | runs-on: ubuntu-24.04 19 | env: 20 | CI_BRANCH: release 21 | PACKAGECLOUD_REPOSITORY: dokku/dokku 22 | VERSION: ${{ github.ref_name }} 23 | 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | 28 | - name: Set up Go 29 | uses: actions/setup-go@v5 30 | with: 31 | go-version-file: "go.mod" 32 | 33 | - name: Get Repository Name 34 | id: repo-name 35 | run: | 36 | echo "REPOSITORY_NAME=$(echo "${{ github.repository }}" | cut -d '/' -f 2)" >> $GITHUB_OUTPUT 37 | echo "GO_VERSION=$(go mod edit -json | jq -r .Go)" >> $GITHUB_OUTPUT 38 | 39 | - name: Build binaries 40 | uses: crazy-max/ghaction-xgo@v3 41 | with: 42 | xgo_version: latest 43 | go_version: "${{ steps.repo-name.outputs.GO_VERSION }}" 44 | dest: dist 45 | prefix: ${{ steps.repo-name.outputs.REPOSITORY_NAME }} 46 | targets: darwin/amd64,darwin/arm64,linux/arm64,linux/amd64,windows/amd64 47 | v: true 48 | x: false 49 | race: false 50 | ldflags: -s -w -X main.Version=${{ github.ref_name }} 51 | buildmode: default 52 | trimpath: true 53 | 54 | - name: Attest Build Provenance - darwin-amd64 55 | uses: actions/attest-build-provenance@v2.3.0 56 | with: 57 | subject-path: "dist/${{ steps.repo-name.outputs.REPOSITORY_NAME }}-darwin-amd64" 58 | 59 | - name: Attest Build Provenance - darwin-arm64 60 | uses: actions/attest-build-provenance@v2.3.0 61 | with: 62 | subject-path: "dist/${{ steps.repo-name.outputs.REPOSITORY_NAME }}-darwin-arm64" 63 | 64 | - name: Attest Build Provenance - linux-amd64 65 | uses: actions/attest-build-provenance@v2.3.0 66 | with: 67 | subject-path: "dist/${{ steps.repo-name.outputs.REPOSITORY_NAME }}-linux-amd64" 68 | 69 | - name: Attest Build Provenance - linux-arm64 70 | uses: actions/attest-build-provenance@v2.3.0 71 | with: 72 | subject-path: "dist/${{ steps.repo-name.outputs.REPOSITORY_NAME }}-linux-arm64" 73 | 74 | - name: Attest Build Provenance - windows-amd64 75 | uses: actions/attest-build-provenance@v2.3.0 76 | with: 77 | subject-path: "dist/${{ steps.repo-name.outputs.REPOSITORY_NAME }}-windows-amd64.exe" 78 | 79 | - name: Setup Ruby 80 | uses: ruby/setup-ruby@v1.244.0 81 | with: 82 | bundler-cache: true 83 | working-directory: .github 84 | 85 | - name: Build Debian Packages 86 | run: | 87 | mkdir -p build/linux 88 | 89 | cp dist/${{ steps.repo-name.outputs.REPOSITORY_NAME }}-linux-amd64 build/linux/${{ steps.repo-name.outputs.REPOSITORY_NAME }}-amd64 90 | cp dist/${{ steps.repo-name.outputs.REPOSITORY_NAME }}-linux-arm64 build/linux/${{ steps.repo-name.outputs.REPOSITORY_NAME }}-arm64 91 | 92 | bundle exec make build/deb/${{ steps.repo-name.outputs.REPOSITORY_NAME }}_${{ github.ref_name }}_arm64.deb 93 | bundle exec make build/deb/${{ steps.repo-name.outputs.REPOSITORY_NAME }}_${{ github.ref_name }}_amd64.deb 94 | cp build/deb/*.deb dist/ 95 | env: 96 | BUNDLE_GEMFILE: .github/Gemfile 97 | 98 | - name: Upload Artifacts 99 | uses: actions/upload-artifact@v4 100 | with: 101 | name: dist 102 | path: dist/* 103 | 104 | - name: Release to PackageCloud 105 | run: bundle exec make release-packagecloud 106 | env: 107 | BUNDLE_GEMFILE: .github/Gemfile 108 | PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }} 109 | 110 | - name: Release 111 | uses: softprops/action-gh-release@v2 112 | with: 113 | files: dist/* 114 | generate_release_notes: true 115 | make_latest: "true" 116 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Build output 15 | build/* 16 | 17 | # Release output 18 | release/* 19 | 20 | # Validation output 21 | validation/* 22 | 23 | # .env files 24 | .env* 25 | 26 | lambda-builder 27 | lambda.zip 28 | 29 | response.json 30 | Procfile -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24.4-bookworm 2 | 3 | # hadolint ignore=DL3027 4 | RUN apt-get update \ 5 | && apt install apt-transport-https build-essential curl gnupg2 jq lintian rsync rubygems-integration ruby-dev ruby -qy \ 6 | && git clone https://github.com/bats-core/bats-core.git /tmp/bats-core \ 7 | && /tmp/bats-core/install.sh /usr/local \ 8 | && apt-get clean \ 9 | && rm -rf /var/lib/apt/lists/* 10 | 11 | # hadolint ignore=DL3028 12 | RUN gem install --quiet rake fpm package_cloud 13 | 14 | WORKDIR /src 15 | 16 | RUN curl -fsSLO https://download.docker.com/linux/static/stable/x86_64/docker-20.10.14.tgz && tar --strip-components=1 -xvzf docker-20.10.14.tgz -C /usr/local/bin 17 | -------------------------------------------------------------------------------- /Dockerfile.hub: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY ./build/linux/lambda-builder-amd64 /lambda-builder 3 | ENTRYPOINT ["/lambda-builder"] 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2022 Jose Diaz-Gonzalez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME = lambda-builder 2 | EMAIL = dokku@josediazgonzalez.com 3 | MAINTAINER = dokku 4 | MAINTAINER_NAME = Jose Diaz-Gonzalez 5 | REPOSITORY = lambda-builder 6 | HARDWARE = $(shell uname -m) 7 | SYSTEM_NAME = $(shell uname -s | tr '[:upper:]' '[:lower:]') 8 | BASE_VERSION ?= 0.6.0 9 | IMAGE_NAME ?= $(MAINTAINER)/$(REPOSITORY) 10 | PACKAGECLOUD_REPOSITORY ?= dokku/dokku-betafish 11 | 12 | ifeq ($(CI_BRANCH),release) 13 | VERSION ?= $(BASE_VERSION) 14 | DOCKER_IMAGE_VERSION = $(VERSION) 15 | else 16 | VERSION = $(shell echo "${BASE_VERSION}")build+$(shell git rev-parse --short HEAD) 17 | DOCKER_IMAGE_VERSION = $(shell echo "${BASE_VERSION}")build-$(shell git rev-parse --short HEAD) 18 | endif 19 | 20 | version: 21 | @echo "$(CI_BRANCH)" 22 | @echo "$(VERSION)" 23 | 24 | define PACKAGE_DESCRIPTION 25 | A tool for building lamda functions into uploadable 26 | zip files via Docker. 27 | endef 28 | 29 | export PACKAGE_DESCRIPTION 30 | 31 | LIST = build release release-packagecloud validate 32 | targets = $(addsuffix -in-docker, $(LIST)) 33 | 34 | .env.docker: 35 | @rm -f .env.docker 36 | @touch .env.docker 37 | @echo "CI_BRANCH=$(CI_BRANCH)" >> .env.docker 38 | @echo "GITHUB_ACCESS_TOKEN=$(GITHUB_ACCESS_TOKEN)" >> .env.docker 39 | @echo "IMAGE_NAME=$(IMAGE_NAME)" >> .env.docker 40 | @echo "PACKAGECLOUD_REPOSITORY=$(PACKAGECLOUD_REPOSITORY)" >> .env.docker 41 | @echo "PACKAGECLOUD_TOKEN=$(PACKAGECLOUD_TOKEN)" >> .env.docker 42 | @echo "VERSION=$(VERSION)" >> .env.docker 43 | 44 | build: prebuild 45 | @$(MAKE) build/darwin/$(NAME)-amd64 46 | @$(MAKE) build/darwin/$(NAME)-arm64 47 | @$(MAKE) build/linux/$(NAME)-amd64 48 | @$(MAKE) build/linux/$(NAME)-arm64 49 | @$(MAKE) build/deb/$(NAME)_$(VERSION)_amd64.deb 50 | @$(MAKE) build/deb/$(NAME)_$(VERSION)_arm64.deb 51 | 52 | build-docker-image: 53 | docker build --rm -q -f Dockerfile -t $(IMAGE_NAME):build . 54 | 55 | $(targets): %-in-docker: .env.docker 56 | docker run \ 57 | --env-file .env.docker \ 58 | --rm \ 59 | --volume /var/lib/docker:/var/lib/docker \ 60 | --volume /var/run/docker.sock:/var/run/docker.sock:ro \ 61 | --volume ${PWD}:/src/github.com/$(MAINTAINER)/$(REPOSITORY) \ 62 | --volume ${PWD}:/home/runner/work/$(REPOSITORY)/$(REPOSITORY) \ 63 | --workdir /src/github.com/$(MAINTAINER)/$(REPOSITORY) \ 64 | $(IMAGE_NAME):build make -e $(@:-in-docker=) 65 | 66 | build/darwin/$(NAME)-amd64: 67 | mkdir -p build/darwin 68 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -a -asmflags=-trimpath=/src -gcflags=-trimpath=/src \ 69 | -ldflags "-s -w -X main.Version=$(VERSION)" \ 70 | -o build/darwin/$(NAME)-amd64 71 | 72 | build/darwin/$(NAME)-arm64: 73 | mkdir -p build/darwin 74 | CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -a -asmflags=-trimpath=/src -gcflags=-trimpath=/src \ 75 | -ldflags "-s -w -X main.Version=$(VERSION)" \ 76 | -o build/darwin/$(NAME)-arm64 77 | 78 | build/linux/$(NAME)-amd64: 79 | mkdir -p build/linux 80 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -asmflags=-trimpath=/src -gcflags=-trimpath=/src \ 81 | -ldflags "-s -w -X main.Version=$(VERSION)" \ 82 | -o build/linux/$(NAME)-amd64 83 | 84 | build/linux/$(NAME)-arm64: 85 | mkdir -p build/linux 86 | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -a -asmflags=-trimpath=/src -gcflags=-trimpath=/src \ 87 | -ldflags "-s -w -X main.Version=$(VERSION)" \ 88 | -o build/linux/$(NAME)-arm64 89 | 90 | build/deb/$(NAME)_$(VERSION)_amd64.deb: build/linux/$(NAME)-amd64 91 | export SOURCE_DATE_EPOCH=$(shell git log -1 --format=%ct) \ 92 | && mkdir -p build/deb \ 93 | && fpm \ 94 | --architecture amd64 \ 95 | --category utils \ 96 | --description "$$PACKAGE_DESCRIPTION" \ 97 | --input-type dir \ 98 | --license 'MIT License' \ 99 | --maintainer "$(MAINTAINER_NAME) <$(EMAIL)>" \ 100 | --name $(NAME) \ 101 | --output-type deb \ 102 | --package build/deb/$(NAME)_$(VERSION)_amd64.deb \ 103 | --url "https://github.com/$(MAINTAINER)/$(REPOSITORY)" \ 104 | --vendor "" \ 105 | --version $(VERSION) \ 106 | --verbose \ 107 | build/linux/$(NAME)-amd64=/usr/bin/$(NAME) \ 108 | LICENSE=/usr/share/doc/$(NAME)/copyright 109 | 110 | build/deb/$(NAME)_$(VERSION)_arm64.deb: build/linux/$(NAME)-arm64 111 | export SOURCE_DATE_EPOCH=$(shell git log -1 --format=%ct) \ 112 | && mkdir -p build/deb \ 113 | && fpm \ 114 | --architecture arm64 \ 115 | --category utils \ 116 | --description "$$PACKAGE_DESCRIPTION" \ 117 | --input-type dir \ 118 | --license 'MIT License' \ 119 | --maintainer "$(MAINTAINER_NAME) <$(EMAIL)>" \ 120 | --name $(NAME) \ 121 | --output-type deb \ 122 | --package build/deb/$(NAME)_$(VERSION)_arm64.deb \ 123 | --url "https://github.com/$(MAINTAINER)/$(REPOSITORY)" \ 124 | --vendor "" \ 125 | --version $(VERSION) \ 126 | --verbose \ 127 | build/linux/$(NAME)-arm64=/usr/bin/$(NAME) \ 128 | LICENSE=/usr/share/doc/$(NAME)/copyright 129 | 130 | clean: 131 | rm -rf build release validation 132 | 133 | ci-report: 134 | docker version 135 | rm -f ~/.gitconfig 136 | 137 | docker-image: 138 | docker build --rm -q -f Dockerfile.hub -t $(IMAGE_NAME):$(DOCKER_IMAGE_VERSION) . 139 | 140 | bin/gh-release: 141 | mkdir -p bin 142 | curl -o bin/gh-release.tgz -sL https://github.com/progrium/gh-release/releases/download/v2.3.3/gh-release_2.3.3_$(SYSTEM_NAME)_$(HARDWARE).tgz 143 | tar xf bin/gh-release.tgz -C bin 144 | chmod +x bin/gh-release 145 | 146 | bin/gh-release-body: 147 | mkdir -p bin 148 | curl -o bin/gh-release-body "https://raw.githubusercontent.com/dokku/gh-release-body/master/gh-release-body" 149 | chmod +x bin/gh-release-body 150 | 151 | release: build bin/gh-release bin/gh-release-body 152 | rm -rf release && mkdir release 153 | tar -zcf release/$(NAME)_$(VERSION)_linux_amd64.tgz -C build/linux $(NAME)-amd64 154 | tar -zcf release/$(NAME)_$(VERSION)_linux_arm64.tgz -C build/linux $(NAME)-arm64 155 | tar -zcf release/$(NAME)_$(VERSION)_darwin_amd64.tgz -C build/darwin $(NAME)-amd64 156 | tar -zcf release/$(NAME)_$(VERSION)_darwin_arm64.tgz -C build/darwin $(NAME)-arm64 157 | cp build/deb/$(NAME)_$(VERSION)_amd64.deb release/$(NAME)_$(VERSION)_amd64.deb 158 | cp build/deb/$(NAME)_$(VERSION)_arm64.deb release/$(NAME)_$(VERSION)_arm64.deb 159 | bin/gh-release create $(MAINTAINER)/$(REPOSITORY) $(VERSION) $(shell git rev-parse --abbrev-ref HEAD) 160 | bin/gh-release-body $(MAINTAINER)/$(REPOSITORY) v$(VERSION) 161 | 162 | release-packagecloud: 163 | @$(MAKE) release-packagecloud-deb 164 | 165 | release-packagecloud-deb: build/deb/$(NAME)_$(VERSION)_amd64.deb build/deb/$(NAME)_$(VERSION)_arm64.deb 166 | package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/focal build/deb/$(NAME)_$(VERSION)_amd64.deb 167 | package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/jammy build/deb/$(NAME)_$(VERSION)_amd64.deb 168 | package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/noble build/deb/$(NAME)_$(VERSION)_amd64.deb 169 | package_cloud push $(PACKAGECLOUD_REPOSITORY)/debian/bullseye build/deb/$(NAME)_$(VERSION)_amd64.deb 170 | package_cloud push $(PACKAGECLOUD_REPOSITORY)/debian/bookworm build/deb/$(NAME)_$(VERSION)_amd64.deb 171 | package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/focal build/deb/$(NAME)_$(VERSION)_arm64.deb 172 | package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/jammy build/deb/$(NAME)_$(VERSION)_arm64.deb 173 | package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/noble build/deb/$(NAME)_$(VERSION)_arm64.deb 174 | package_cloud push $(PACKAGECLOUD_REPOSITORY)/debian/bullseye build/deb/$(NAME)_$(VERSION)_arm64.deb 175 | package_cloud push $(PACKAGECLOUD_REPOSITORY)/debian/bookworm build/deb/$(NAME)_$(VERSION)_arm64.deb 176 | 177 | validate: 178 | mkdir -p validation 179 | lintian build/deb/$(NAME)_$(VERSION)_amd64.deb || true 180 | lintian build/deb/$(NAME)_$(VERSION)_arm64.deb || true 181 | dpkg-deb --info build/deb/$(NAME)_$(VERSION)_amd64.deb 182 | dpkg-deb --info build/deb/$(NAME)_$(VERSION)_arm64.deb 183 | dpkg -c build/deb/$(NAME)_$(VERSION)_amd64.deb 184 | dpkg -c build/deb/$(NAME)_$(VERSION)_arm64.deb 185 | cd validation && ar -x ../build/deb/$(NAME)_$(VERSION)_amd64.deb 186 | cd validation && ar -x ../build/deb/$(NAME)_$(VERSION)_arm64.deb 187 | ls -lah build/deb validation 188 | sha1sum build/deb/$(NAME)_$(VERSION)_amd64.deb 189 | sha1sum build/deb/$(NAME)_$(VERSION)_arm64.deb 190 | cd /home/runner/work/$(REPOSITORY)/$(REPOSITORY) && bats test.bats 191 | 192 | prebuild: 193 | git config --global --add safe.directory $(shell pwd) 194 | git status 195 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lambda-builder 2 | 3 | A tool for building lamda functions into uploadable zip files via Docker based on work from [@lambci](https://github.com/lambci/docker-lambda) and [@mLupine](https://github.com/mLupine/docker-lambda). 4 | 5 | ## Why? 6 | 7 | I don't want to go through the motions of figuring out the correct way to build my app for AWS. I suspect there are others out there who feel the same. 8 | 9 | ## Dependencies 10 | 11 | - The `docker` binary 12 | - Golang 1.7+ 13 | 14 | ## Building 15 | 16 | ```shell 17 | # substitute the version number as desired 18 | go build -ldflags "-X main.Version=0.5.0" 19 | ``` 20 | 21 | ## Usage 22 | 23 | ```text 24 | Usage: lambda-builder [--version] [--help] [] 25 | 26 | Available commands are: 27 | build Builds a lambda function 28 | version Return the version of the binary 29 | ``` 30 | 31 | ### Building an app 32 | 33 | To build an app: 34 | 35 | ```shell 36 | cd path/to/app 37 | 38 | # will write a lambda.zip in the current working directory 39 | lambda-builder build 40 | ``` 41 | 42 | Alternatively, a given path can be specified via the `--working-directory` flag: 43 | 44 | ```shell 45 | # will write a lambda.zip in the specified path 46 | lambda-builder build --working-directory path/to/app 47 | ``` 48 | 49 | Custom environment variables can be supplied for the build environment by specifying one or more `--build-env` flags. The `--build-env` flag takes `KEY=VALUE` pairs. 50 | 51 | ```shell 52 | # the build step will have access to both the --build-env pairs 53 | lambda-builder build --build-env KEY=VALUE --build-env ANOTHER_KEY=some-value 54 | ``` 55 | 56 | A `builder` can be chosen by a flag. Note that while a `builder` may be selected, the detection for that builder must still pass in order for the build to succeed. 57 | 58 | ```shell 59 | lambda-builder build --generate-image --builder dotnet 60 | ``` 61 | 62 | #### Building an image 63 | 64 | A docker image can be produced from the generated artifact by specifying the `--generate-image` flag. This also allows for multiple `--label` flags as well as specifying a single image tag via either `-t` or `--tag`: 65 | 66 | ```shell 67 | # will write a lambda.zip in the specified path 68 | # and generate a docker image named `lambda-builder/$APP:latest` 69 | # where $APP is the last portion of the working directory 70 | lambda-builder build --generate-image 71 | 72 | # adds the labels com.example/key=value and com.example/another-key=value 73 | lambda-builder build --generate-image --label com.example/key=value --label com.example/another-key=value 74 | 75 | # tags the image as app/awesome:1234 76 | lambda-builder build --generate-image --tag app/awesome:1234 77 | ``` 78 | 79 | By default, any web process started by the built image starts on port `9001`. This can be overriden via the `--port` environment variable. 80 | 81 | ```shell 82 | # build the image and ensure it starts on port 5000 by default 83 | lambda-builder build --generate-image --port 5000 84 | ``` 85 | 86 | Custom environment variables can be supplied for the built image by specifying one or more `--image-env` flags. The `--image-env` flag takes `KEY=VALUE` pairs. 87 | 88 | ```shell 89 | # the built image will have `ENV` directives corresponding to the values specified by `--image-env` 90 | lambda-builder build --generate-image --image-env KEY=VALUE --image-env ANOTHER_KEY=some-value 91 | ``` 92 | 93 | The `build-image` and `run-image` can also be specified as flags: 94 | 95 | ```shell 96 | lambda-builder build --generate-image --build-image "mlupin/docker-lambda:dotnetcore3.1-build" --run-image "mlupin/docker-lambda:dotnetcore3.1" 97 | ``` 98 | 99 | A generated image can be run locally with the following line: 100 | 101 | ```shell 102 | # run the container and ensure it stays open 103 | # replace `$APP` with your folder name 104 | docker run --rm -it -e DOCKER_LAMBDA_STAY_OPEN=1 -p 9001:9001 "lambda-builder/$APP:latest" 105 | 106 | # invoke it using the awscli (v2) 107 | # note that the function name in this example is `function.handler` 108 | aws lambda invoke --endpoint http://localhost:9001 --no-sign-request --function-name function.handler --payload '{}' --cli-binary-format raw-in-base64-out output.json 109 | 110 | # invoke it via curl 111 | curl -d '{}' http://localhost:9001/2015-03-31/functions/function.handler/invocations 112 | 113 | # the function can also be invoked directly from a container if desired 114 | docker run --rm "lambda-builder/$APP:latest" function.handler '{"name": "World"}' 115 | ``` 116 | 117 | #### Generating a Procfile 118 | 119 | A `Procfile` can be written to the working directory by specifying the `--write-procfile` flag. This file will not be written if one already exists in the working directory. If an image is being built, the detected handler will also be injected into the build context and used as the default `CMD` for the image. The contents of the `Procfile` are a `web` process type and a detected handler. 120 | 121 | ```shell 122 | # writes out a procfile 123 | lambda-builder build --write-procfile 124 | ``` 125 | 126 | A `--handler` flag can be specified with a custom handler to override the one detected. 127 | 128 | ```shell 129 | # override with a custom handler 130 | lambda-builder build --write-procfile --handler foo_file.bar_func 131 | ``` 132 | 133 | ### How does it work 134 | 135 | Internally, `lambda-builder` detects a given language and builds the app according to the script specified by the detected builder within a disposablecontainer environment emulating AWS Lambda. If a builder is not detected, the build will fail. The following languages are supported: 136 | 137 | - `dotnet` 138 | - default build image: `mlupin/docker-lambda:dotnet6-build` 139 | - requirement: `Function.cs` 140 | - runtimes: 141 | - dotnet6 142 | - dotnetcore3.1 143 | - `go` 144 | - default build image: 145 | - With `go.mod`: `golang:1.22-bookworm` 146 | - Without `go.mod`: `golang:1.17-buster` 147 | - requirement: `go.mod` or `main.go` 148 | - runtimes: 149 | - provided.al2 150 | - `nodejs` 151 | - default build image: `mlupin/docker-lambda:nodejs14.x-build` 152 | - requirement: `package-lock.json` 153 | - runtimes: 154 | - nodejs12.x 155 | - nodejs14.x 156 | - `python` 157 | - default build image: `mlupin/docker-lambda:python3.9-build` 158 | - requirement: `requirements.txt`, `poetry.lock`, or `Pipfile.lock` 159 | - notes: Autodetects the python version from `poetry.lock`, `Pipfile.lock`, or `runtime.txt` 160 | - runtimes: 161 | - python3.8 162 | - python3.9 163 | - `ruby` 164 | - default build image: `mlupin/docker-lambda:ruby2.7-build` 165 | - requirement: `Gemfile.lock` 166 | - runtimes: 167 | - ruby2.7 168 | 169 | All builders support both pre (run before the app is compiled) and post (run after the app is compiled but before it is compressed into a `lambda.zip` file) compile hooks in the form of `bin/pre_compile` and `bin/post_compile`. These can be shell scripts or executables. 170 | 171 | When the app is built, a `lambda.zip` will be produced in the specified working directory. The resulting `lambda.zip` can be uploaded to S3 and used within a Lambda function. 172 | 173 | Both the builder, build image environment, and the run image environment can be overriden in an optional `lambda.yml` file in the specified working directory. 174 | 175 | ### `lambda.yml` 176 | 177 | The following a short description of the `lambda.yml` format. 178 | 179 | ```yaml 180 | --- 181 | build_image: mlupin/docker-lambda:dotnetcore3.1-build 182 | builder: dotnet 183 | run_image: mlupin/docker-lambda:dotnetcore3.1 184 | ``` 185 | 186 | - `build_image`: A docker image that is accessible by the docker daemon. The `build_image` _should_ be based on an existing Lambda image - builders may fail if they cannot run within the specified `build_image`. The build will fail if the image is inaccessible by the docker daemon. 187 | - `builder`: The name of a builder. This may be used if multiple builders match and a specific builder is desired. If an invalid builder is specified, the build will fail. 188 | - `run_image`: A docker image that is accessible by the docker daemon. The `run_image` _should_ be based on an existing Lambda image - built images may fail to start if they are not compatible with the produced artifact. The generation of the `run` iage will fail if the image is inaccessible by the docker daemon. 189 | 190 | ### Deploying 191 | 192 | The `lambda.zip` file can be directly uploaded to a lambda function and used as is by specifying the correct runtime. See the `test.bats` files in any of the `test` examples for more info on how to perform this with the `awscli` (v2). 193 | 194 | ## Examples 195 | 196 | See the `tests` directory for examples on how to use this project. 197 | -------------------------------------------------------------------------------- /builders/dotnet.go: -------------------------------------------------------------------------------- 1 | package builders 2 | 3 | import "lambda-builder/io" 4 | 5 | type DotnetBuilder struct { 6 | Config Config 7 | } 8 | 9 | func NewDotnetBuilder(config Config) (DotnetBuilder, error) { 10 | var err error 11 | config.BuilderBuildImage, err = getBuildImage(config, "mlupin/docker-lambda:dotnet6-build") 12 | if err != nil { 13 | return DotnetBuilder{}, err 14 | } 15 | 16 | config.BuilderRunImage, err = getRunImage(config, "mlupin/docker-lambda:dotnet6") 17 | if err != nil { 18 | return DotnetBuilder{}, err 19 | } 20 | 21 | return DotnetBuilder{ 22 | Config: config, 23 | }, nil 24 | } 25 | 26 | func (b DotnetBuilder) Detect() bool { 27 | if io.FileExistsInDirectory(b.Config.WorkingDirectory, "Function.cs") { 28 | return true 29 | } 30 | 31 | return false 32 | } 33 | 34 | func (b DotnetBuilder) Execute() error { 35 | b.Config.HandlerMap = b.GetHandlerMap() 36 | return executeBuilder(b.script(), b.Config) 37 | } 38 | 39 | func (b DotnetBuilder) GetBuildImage() string { 40 | return b.Config.BuilderBuildImage 41 | } 42 | 43 | func (b DotnetBuilder) GetConfig() Config { 44 | return b.Config 45 | } 46 | 47 | func (b DotnetBuilder) GetHandlerMap() map[string]string { 48 | return map[string]string{} 49 | } 50 | 51 | func (b DotnetBuilder) Name() string { 52 | return "dotnet" 53 | } 54 | 55 | func (b DotnetBuilder) script() string { 56 | return ` 57 | #!/usr/bin/env bash 58 | set -eo pipefail 59 | 60 | [ "$BUILDER_XTRACE" ] && set -o xtrace 61 | 62 | indent() { 63 | sed -u "s/^/ /" 64 | } 65 | 66 | puts-step() { 67 | echo "-----> $*" 68 | } 69 | 70 | install-dotnet() { 71 | puts-step "Compiling via dotnet publish" 72 | dotnet publish -c Release -o pub 2>&1 | indent 73 | } 74 | 75 | hook-pre-compile() { 76 | if [[ ! -f bin/pre_compile ]]; then 77 | return 78 | fi 79 | 80 | puts-step "Running pre-compile hook" 81 | chmod +x bin/pre_compile 82 | bin/pre_compile 83 | } 84 | 85 | hook-post-compile() { 86 | if [[ ! -f bin/post_compile ]]; then 87 | return 88 | fi 89 | 90 | puts-step "Running post-compile hook" 91 | chmod +x bin/post_compile 92 | bin/post_compile 93 | } 94 | 95 | hook-package() { 96 | if [[ "$LAMBDA_BUILD_ZIP" != "1" ]]; then 97 | return 98 | fi 99 | 100 | puts-step "Creating package at lambda.zip" 101 | zip -q -r lambda.zip ./* 102 | } 103 | 104 | hook-pre-compile 105 | install-dotnet 106 | hook-post-compile 107 | hook-package 108 | ` 109 | } 110 | -------------------------------------------------------------------------------- /builders/go.go: -------------------------------------------------------------------------------- 1 | package builders 2 | 3 | import "lambda-builder/io" 4 | 5 | type GoBuilder struct { 6 | Config Config 7 | } 8 | 9 | func NewGoBuilder(config Config) (GoBuilder, error) { 10 | var err error 11 | defaultBuilder := "golang:1.22-bookworm" 12 | if !io.FileExistsInDirectory(config.WorkingDirectory, "go.mod") { 13 | defaultBuilder = "golang:1.17-bullseye" 14 | } 15 | 16 | config.BuilderBuildImage, err = getBuildImage(config, defaultBuilder) 17 | if err != nil { 18 | return GoBuilder{}, err 19 | } 20 | 21 | config.BuilderRunImage, err = getRunImage(config, "mlupin/docker-lambda:provided.al2") 22 | if err != nil { 23 | return GoBuilder{}, err 24 | } 25 | 26 | return GoBuilder{ 27 | Config: config, 28 | }, nil 29 | } 30 | 31 | func (b GoBuilder) Detect() bool { 32 | if io.FileExistsInDirectory(b.Config.WorkingDirectory, "go.mod") { 33 | return true 34 | } 35 | 36 | if io.FileExistsInDirectory(b.Config.WorkingDirectory, "main.go") { 37 | return true 38 | } 39 | 40 | return false 41 | } 42 | 43 | func (b GoBuilder) Execute() error { 44 | b.Config.HandlerMap = b.GetHandlerMap() 45 | return executeBuilder(b.script(), b.Config) 46 | } 47 | 48 | func (b GoBuilder) GetBuildImage() string { 49 | return b.Config.BuilderBuildImage 50 | } 51 | 52 | func (b GoBuilder) GetConfig() Config { 53 | return b.Config 54 | } 55 | 56 | func (b GoBuilder) GetHandlerMap() map[string]string { 57 | return map[string]string{ 58 | "bootstrap": "bootstrap", 59 | } 60 | } 61 | 62 | func (b GoBuilder) Name() string { 63 | return "go" 64 | } 65 | 66 | func (b GoBuilder) script() string { 67 | return ` 68 | #!/usr/bin/env bash 69 | set -eo pipefail 70 | 71 | [ "$BUILDER_XTRACE" ] && set -o xtrace 72 | 73 | indent() { 74 | sed -u "s/^/ /" 75 | } 76 | 77 | puts-step() { 78 | echo "-----> $*" 79 | } 80 | 81 | install-gomod() { 82 | if [[ -f "go.mod" ]]; then 83 | puts-step "Downloading dependencies via go mod" 84 | go mod download 2>&1 | indent 85 | else 86 | puts-step "Missing go.mod, downloading dependencies via go get" 87 | go env -w GO111MODULE=off 88 | go get 89 | fi 90 | 91 | puts-step "Compiling via go build" 92 | CGO_ENABLED=0 go build -o bootstrap main.go 2>&1 | indent 93 | } 94 | 95 | hook-pre-compile() { 96 | if [[ ! -f bin/pre_compile ]]; then 97 | return 98 | fi 99 | 100 | puts-step "Running pre-compile hook" 101 | chmod +x bin/pre_compile 102 | bin/pre_compile 103 | } 104 | 105 | hook-post-compile() { 106 | if [[ ! -f bin/post_compile ]]; then 107 | return 108 | fi 109 | 110 | puts-step "Running post-compile hook" 111 | chmod +x bin/post_compile 112 | bin/post_compile 113 | } 114 | 115 | hook-package() { 116 | if [[ "$LAMBDA_BUILD_ZIP" != "1" ]]; then 117 | return 118 | fi 119 | 120 | if ! command -v zip >/dev/null 2>&1; then 121 | puts-step "Installing zip dependency for packaging" 122 | apt update && apt install -y --no-install-recommends zip 123 | fi 124 | 125 | puts-step "Creating package at lambda.zip" 126 | zip -q -r lambda.zip bootstrap 127 | mv lambda.zip /var/task/lambda.zip 128 | } 129 | 130 | cp -a /var/task/. /go/src/handler 131 | cd /go/src/handler 132 | hook-pre-compile 133 | install-gomod 134 | hook-post-compile 135 | hook-package 136 | ` 137 | } 138 | -------------------------------------------------------------------------------- /builders/handler.go: -------------------------------------------------------------------------------- 1 | package builders 2 | 3 | import ( 4 | "fmt" 5 | "lambda-builder/io" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | func getFunctionHandler(directory string, config Config) string { 11 | if config.Handler != "" { 12 | return config.Handler 13 | } 14 | 15 | for file, handler := range config.HandlerMap { 16 | if io.FileExistsInDirectory(directory, file) { 17 | return handler 18 | } 19 | } 20 | 21 | return "" 22 | } 23 | 24 | func writeProcfile(handler string, directory string) error { 25 | b := []byte(fmt.Sprintf("web: %s\n", handler)) 26 | return os.WriteFile(filepath.Join(directory, "Procfile"), b, 0644) 27 | } 28 | -------------------------------------------------------------------------------- /builders/main.go: -------------------------------------------------------------------------------- 1 | package builders 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "html/template" 8 | "io/ioutil" 9 | "os" 10 | "os/signal" 11 | "path/filepath" 12 | "strconv" 13 | "strings" 14 | 15 | "lambda-builder/io" 16 | 17 | execute "github.com/alexellis/go-execute/pkg/v2" 18 | extract "github.com/codeclysm/extract/v4" 19 | "gopkg.in/yaml.v2" 20 | ) 21 | 22 | type Builder interface { 23 | Detect() bool 24 | Execute() error 25 | GetBuildImage() string 26 | GetConfig() Config 27 | GetHandlerMap() map[string]string 28 | Name() string 29 | } 30 | 31 | type Config struct { 32 | BuildEnv []string 33 | Builder string 34 | BuilderBuildImage string 35 | BuilderRunImage string 36 | GenerateRunImage bool 37 | Handler string 38 | HandlerMap map[string]string 39 | Identifier string 40 | ImageEnv []string 41 | ImageLabels []string 42 | ImageTag string 43 | Port int 44 | RunQuiet bool 45 | WorkingDirectory string 46 | WriteProcfile bool 47 | } 48 | 49 | func (c Config) GetImageTag() string { 50 | if c.ImageTag != "" { 51 | return c.ImageTag 52 | } 53 | 54 | appName := filepath.Base(c.WorkingDirectory) 55 | return fmt.Sprintf("lambda-builder/%s:latest", appName) 56 | } 57 | 58 | type LambdaYML struct { 59 | Builder string `yaml:"builder"` 60 | BuildImage string `yaml:"build_image"` 61 | RunImage string `yaml:"run_image"` 62 | } 63 | 64 | func executeBuilder(script string, config Config) error { 65 | if err := executeBuildContainer(script, config); err != nil { 66 | return err 67 | } 68 | 69 | taskHostBuildDir, err := os.MkdirTemp("", "lambda-builder") 70 | if err != nil { 71 | return fmt.Errorf("error creating build dir: %w", err) 72 | } 73 | 74 | defer func() { 75 | os.RemoveAll(taskHostBuildDir) 76 | }() 77 | 78 | fmt.Printf("-----> Extracting lambda.zip into build context dir\n") 79 | zipPath := filepath.Join(config.WorkingDirectory, "lambda.zip") 80 | data, _ := ioutil.ReadFile(zipPath) 81 | buffer := bytes.NewBuffer(data) 82 | if err := extract.Zip(context.Background(), buffer, taskHostBuildDir, nil); err != nil { 83 | return fmt.Errorf("error extracting lambda.zip into build context dir: %w", err) 84 | } 85 | 86 | handler := getFunctionHandler(taskHostBuildDir, config) 87 | if config.WriteProcfile && !io.FileExistsInDirectory(taskHostBuildDir, "Procfile") { 88 | if handler == "" { 89 | fmt.Printf(" ! Unable to detect handler in build directory\n") 90 | } else { 91 | fmt.Printf("=====> Writing Procfile from handler: %s\n", handler) 92 | 93 | fmt.Printf(" Writing to working directory\n") 94 | if err := writeProcfile(handler, config.WorkingDirectory); err != nil { 95 | return fmt.Errorf("error writing Procfile to working directory: %w", err) 96 | } 97 | 98 | fmt.Printf(" Writing to build directory\n") 99 | if err := writeProcfile(handler, taskHostBuildDir); err != nil { 100 | return fmt.Errorf("error writing Procfile to temporary build directory: %w", err) 101 | } 102 | } 103 | } 104 | 105 | if config.GenerateRunImage { 106 | fmt.Printf("=====> Building image\n") 107 | fmt.Printf(" Generating temporary Dockerfile\n") 108 | 109 | dockerfilePath, err := ioutil.TempFile("", "lambda-builder") 110 | defer func() { 111 | os.Remove(dockerfilePath.Name()) 112 | }() 113 | 114 | if err != nil { 115 | return fmt.Errorf("error generating temporary Dockerfile: %w", err) 116 | } 117 | 118 | if err := generateRunDockerfile(handler, config, dockerfilePath); err != nil { 119 | return err 120 | } 121 | 122 | fmt.Printf(" Executing build of %s\n", config.GetImageTag()) 123 | if err := buildDockerImage(taskHostBuildDir, config, "run", dockerfilePath); err != nil { 124 | return err 125 | } 126 | } 127 | 128 | return nil 129 | } 130 | 131 | func executeBuildContainer(script string, config Config) error { 132 | fmt.Printf(" Generating temporary build script\n") 133 | scriptPath, err := os.Create(filepath.Join(config.WorkingDirectory, ".lambda-builder")) 134 | defer func() { 135 | os.Remove(scriptPath.Name()) 136 | }() 137 | 138 | if err != nil { 139 | return fmt.Errorf("error generating temporary build script: %w", err) 140 | } 141 | 142 | if _, err := scriptPath.WriteString(strings.TrimSpace(script)); err != nil { 143 | return err 144 | } 145 | 146 | fmt.Printf(" Generating temporary Dockerfile\n") 147 | dockerfilePath, err := ioutil.TempFile("", "lambda-builder") 148 | defer func() { 149 | os.Remove(dockerfilePath.Name()) 150 | }() 151 | 152 | if err != nil { 153 | return fmt.Errorf("error generating temporary Dockerfile: %w", err) 154 | } 155 | 156 | if err := generateBuildDockerfile(config, dockerfilePath, scriptPath); err != nil { 157 | return err 158 | } 159 | 160 | fmt.Printf(" Executing build of %s\n", config.GetImageTag()) 161 | if err := buildDockerImage(config.WorkingDirectory, config, "build", dockerfilePath); err != nil { 162 | return err 163 | } 164 | 165 | defer func() { 166 | buildImageTag := fmt.Sprintf("%s-build", config.GetImageTag()) 167 | fmt.Printf(" Removing build image: %s", buildImageTag) 168 | args := []string{ 169 | "image", 170 | "rm", 171 | "--force", 172 | buildImageTag, 173 | } 174 | 175 | signals := make(chan os.Signal, 1) 176 | signal.Notify(signals, os.Interrupt) 177 | ctx, cancel := context.WithCancel(context.Background()) 178 | go func() { 179 | <-signals 180 | cancel() 181 | }() 182 | 183 | cmd := execute.ExecTask{ 184 | Args: args, 185 | Command: "docker", 186 | Cwd: config.WorkingDirectory, 187 | StreamStdio: !config.RunQuiet, 188 | } 189 | 190 | if _, err := cmd.Execute(ctx); err != nil { 191 | fmt.Printf(" Error cleaning up build image: %s", err.Error()) 192 | } 193 | }() 194 | 195 | extractLambdaFromBuildImage(config) 196 | 197 | return nil 198 | } 199 | 200 | func generateBuildDockerfile(config Config, dockerfilePath *os.File, scriptPath *os.File) error { 201 | tpl, err := template.New("t1").Parse(` 202 | FROM {{ .build_image }} 203 | LABEL com.dokku.lambda-builder/builder={{ .builder_name }} 204 | ENV LAMBDA_BUILD_ZIP=1 205 | WORKDIR /var/task 206 | COPY . /var/task 207 | {{range .env}} 208 | ENV {{.}} 209 | {{end}} 210 | RUN mv {{ .build_script_name }} /usr/local/bin/build-lambda && \ 211 | chmod +x /usr/local/bin/build-lambda && \ 212 | head -n1 /usr/local/bin/build-lambda && \ 213 | /usr/local/bin/build-lambda 214 | `) 215 | if err != nil { 216 | return fmt.Errorf("error generating template: %s", err) 217 | } 218 | 219 | data := map[string]interface{}{ 220 | "build_script_name": filepath.Base(scriptPath.Name()), 221 | "env": config.BuildEnv, 222 | "builder": config.Builder, 223 | "build_image": config.BuilderBuildImage, 224 | } 225 | 226 | if err := tpl.Execute(dockerfilePath, data); err != nil { 227 | return fmt.Errorf("error writing Dockerfile: %s", err) 228 | } 229 | 230 | return nil 231 | } 232 | 233 | func extractLambdaFromBuildImage(config Config) error { 234 | args := []string{ 235 | "container", 236 | "run", 237 | "--rm", 238 | "--label", "com.dokku.lambda-builder/extractor=true", 239 | "--name", fmt.Sprintf("lambda-builder-extractor-%s", config.Identifier), 240 | "--volume", fmt.Sprintf("%s:/tmp/task", config.WorkingDirectory), 241 | } 242 | 243 | args = append(args, fmt.Sprintf("%s-build", config.GetImageTag()), "/bin/bash", "-c", "mv /var/task/lambda.zip /tmp/task/lambda.zip") 244 | 245 | signals := make(chan os.Signal, 1) 246 | signal.Notify(signals, os.Interrupt) 247 | ctx, cancel := context.WithCancel(context.Background()) 248 | go func() { 249 | <-signals 250 | cancel() 251 | }() 252 | 253 | cmd := execute.ExecTask{ 254 | Args: args, 255 | Command: "docker", 256 | Cwd: config.WorkingDirectory, 257 | StreamStdio: !config.RunQuiet, 258 | } 259 | 260 | res, err := cmd.Execute(ctx) 261 | if err != nil { 262 | return fmt.Errorf("error executing builder: %w", err) 263 | } 264 | 265 | if res.ExitCode != 0 { 266 | return fmt.Errorf("error executing builder, exit code %d", res.ExitCode) 267 | } 268 | 269 | return nil 270 | } 271 | 272 | func generateRunDockerfile(cmd string, config Config, dockerfilePath *os.File) error { 273 | tpl, err := template.New("t1").Parse(` 274 | FROM {{ .run_image }} 275 | {{ if ne .port "-1" }} 276 | ENV DOCKER_LAMBDA_API_PORT={{ .port }} 277 | ENV DOCKER_LAMBDA_RUNTIME_PORT={{ .port }} 278 | {{ end }} 279 | {{range .env}} 280 | ENV {{.}} 281 | {{end}} 282 | {{ if ne .command "" }} 283 | CMD ["{{ .cmd }}"] 284 | {{ end }} 285 | COPY . /var/task 286 | `) 287 | if err != nil { 288 | return fmt.Errorf("error generating template: %s", err) 289 | } 290 | 291 | data := map[string]interface{}{ 292 | "cmd": cmd, 293 | "env": config.ImageEnv, 294 | "port": strconv.Itoa(config.Port), 295 | "run_image": config.BuilderRunImage, 296 | } 297 | 298 | if err := tpl.Execute(dockerfilePath, data); err != nil { 299 | return fmt.Errorf("error writing Dockerfile: %s", err) 300 | } 301 | 302 | return nil 303 | } 304 | 305 | func buildDockerImage(directory string, config Config, phase string, dockerfilePath *os.File) error { 306 | imageTag := config.GetImageTag() 307 | if phase == "build" { 308 | imageTag = fmt.Sprintf("%s-build", config.GetImageTag()) 309 | } 310 | 311 | args := []string{ 312 | "image", 313 | "build", 314 | "--file", dockerfilePath.Name(), 315 | "--progress", "plain", 316 | "--tag", imageTag, 317 | } 318 | 319 | if phase == "run" { 320 | for _, label := range config.ImageLabels { 321 | args = append(args, "--label", label) 322 | } 323 | } 324 | 325 | args = append(args, directory) 326 | 327 | signals := make(chan os.Signal, 1) 328 | signal.Notify(signals, os.Interrupt) 329 | ctx, cancel := context.WithCancel(context.Background()) 330 | go func() { 331 | <-signals 332 | cancel() 333 | }() 334 | 335 | cmd := execute.ExecTask{ 336 | Args: args, 337 | Command: "docker", 338 | Cwd: config.WorkingDirectory, 339 | StreamStdio: !config.RunQuiet, 340 | } 341 | 342 | res, err := cmd.Execute(ctx) 343 | if err != nil { 344 | return fmt.Errorf("error building image: %w", err) 345 | } 346 | 347 | if res.ExitCode != 0 { 348 | return fmt.Errorf("error building image, exit code %d", res.ExitCode) 349 | } 350 | 351 | return nil 352 | } 353 | 354 | func ParseLambdaYML(config Config) (LambdaYML, error) { 355 | var lambdaYML LambdaYML 356 | if !io.FileExistsInDirectory(config.WorkingDirectory, "lambda.yml") { 357 | return lambdaYML, nil 358 | } 359 | 360 | f, err := os.Open(filepath.Join(config.WorkingDirectory, "lambda.yml")) 361 | if err != nil { 362 | return lambdaYML, fmt.Errorf("error opening lambda.yml: %w", err) 363 | } 364 | defer f.Close() 365 | 366 | bytes, err := ioutil.ReadAll(f) 367 | if err != nil { 368 | return lambdaYML, fmt.Errorf("error reading lambda.yml: %w", err) 369 | } 370 | 371 | if err := yaml.Unmarshal(bytes, &lambdaYML); err != nil { 372 | return lambdaYML, fmt.Errorf("error unmarshaling lambda.yml: %w", err) 373 | } 374 | 375 | return lambdaYML, nil 376 | } 377 | 378 | func getBuildImage(config Config, defaultImage string) (string, error) { 379 | if config.BuilderBuildImage != "" { 380 | return config.BuilderBuildImage, nil 381 | } 382 | 383 | lambdaYML, err := ParseLambdaYML(config) 384 | if err != nil { 385 | return "", err 386 | } 387 | 388 | if lambdaYML.BuildImage == "" { 389 | return defaultImage, nil 390 | } 391 | 392 | return lambdaYML.BuildImage, nil 393 | } 394 | 395 | func getRunImage(config Config, defaultImage string) (string, error) { 396 | if config.BuilderRunImage != "" { 397 | return config.BuilderRunImage, nil 398 | } 399 | 400 | lambdaYML, err := ParseLambdaYML(config) 401 | if err != nil { 402 | return "", err 403 | } 404 | 405 | if lambdaYML.RunImage == "" { 406 | return defaultImage, nil 407 | } 408 | 409 | return lambdaYML.RunImage, nil 410 | } 411 | -------------------------------------------------------------------------------- /builders/nodejs.go: -------------------------------------------------------------------------------- 1 | package builders 2 | 3 | import "lambda-builder/io" 4 | 5 | type NodejsBuilder struct { 6 | Config Config 7 | } 8 | 9 | func NewNodejsBuilder(config Config) (NodejsBuilder, error) { 10 | var err error 11 | config.BuilderBuildImage, err = getBuildImage(config, "mlupin/docker-lambda:nodejs14.x-build") 12 | if err != nil { 13 | return NodejsBuilder{}, err 14 | } 15 | 16 | config.BuilderRunImage, err = getRunImage(config, "mlupin/docker-lambda:nodejs14.x") 17 | if err != nil { 18 | return NodejsBuilder{}, err 19 | } 20 | 21 | return NodejsBuilder{ 22 | Config: config, 23 | }, nil 24 | } 25 | 26 | func (b NodejsBuilder) Detect() bool { 27 | if io.FileExistsInDirectory(b.Config.WorkingDirectory, "package-lock.json") { 28 | return true 29 | } 30 | 31 | return false 32 | } 33 | 34 | func (b NodejsBuilder) Execute() error { 35 | b.Config.HandlerMap = b.GetHandlerMap() 36 | return executeBuilder(b.script(), b.Config) 37 | } 38 | 39 | func (b NodejsBuilder) GetBuildImage() string { 40 | return b.Config.BuilderBuildImage 41 | } 42 | 43 | func (b NodejsBuilder) GetConfig() Config { 44 | return b.Config 45 | } 46 | 47 | func (b NodejsBuilder) GetHandlerMap() map[string]string { 48 | return map[string]string{ 49 | "function.js": "function.handler", 50 | "lambda_function.js": "lambda_function.handler", 51 | } 52 | } 53 | 54 | func (b NodejsBuilder) Name() string { 55 | return "nodejs" 56 | } 57 | 58 | func (b NodejsBuilder) script() string { 59 | return ` 60 | #!/usr/bin/env bash 61 | set -eo pipefail 62 | 63 | [ "$BUILDER_XTRACE" ] && set -o xtrace 64 | 65 | indent() { 66 | sed -u "s/^/ /" 67 | } 68 | 69 | puts-step() { 70 | echo "-----> $*" 71 | } 72 | 73 | install-npm() { 74 | puts-step "Installing dependencies via npm" 75 | npm install 2>&1 | indent 76 | } 77 | 78 | hook-pre-compile() { 79 | if [[ ! -f bin/pre_compile ]]; then 80 | return 81 | fi 82 | 83 | puts-step "Running pre-compile hook" 84 | chmod +x bin/pre_compile 85 | bin/pre_compile 86 | } 87 | 88 | hook-post-compile() { 89 | if [[ ! -f bin/post_compile ]]; then 90 | return 91 | fi 92 | 93 | puts-step "Running post-compile hook" 94 | chmod +x bin/post_compile 95 | bin/post_compile 96 | } 97 | 98 | hook-package() { 99 | if [[ "$LAMBDA_BUILD_ZIP" != "1" ]]; then 100 | return 101 | fi 102 | 103 | puts-step "Creating package at lambda.zip" 104 | zip -q -r lambda.zip ./* 105 | } 106 | 107 | hook-pre-compile 108 | install-npm 109 | hook-post-compile 110 | hook-package 111 | ` 112 | } 113 | -------------------------------------------------------------------------------- /builders/python.go: -------------------------------------------------------------------------------- 1 | package builders 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | 11 | "lambda-builder/io" 12 | 13 | "github.com/BurntSushi/toml" 14 | "github.com/Masterminds/semver" 15 | ) 16 | 17 | type PythonBuilder struct { 18 | Config Config 19 | } 20 | 21 | func NewPythonBuilder(config Config) (PythonBuilder, error) { 22 | var err error 23 | version := "3.9" 24 | if config.BuilderBuildImage == "" || config.BuilderRunImage == "" { 25 | version, err = parsePythonVersion(config.WorkingDirectory, []string{"3.8", "3.9"}) 26 | if err != nil { 27 | return PythonBuilder{}, err 28 | } 29 | } 30 | 31 | config.BuilderBuildImage, err = getBuildImage(config, fmt.Sprintf("mlupin/docker-lambda:python%s-build", version)) 32 | if err != nil { 33 | return PythonBuilder{}, err 34 | } 35 | 36 | config.BuilderRunImage, err = getRunImage(config, fmt.Sprintf("mlupin/docker-lambda:python%s", version)) 37 | if err != nil { 38 | return PythonBuilder{}, err 39 | } 40 | 41 | return PythonBuilder{ 42 | Config: config, 43 | }, nil 44 | } 45 | 46 | func (b PythonBuilder) Detect() bool { 47 | if io.FileExistsInDirectory(b.Config.WorkingDirectory, "requirements.txt") { 48 | return true 49 | } 50 | 51 | if io.FileExistsInDirectory(b.Config.WorkingDirectory, "poetry.lock") { 52 | return true 53 | } 54 | 55 | if io.FileExistsInDirectory(b.Config.WorkingDirectory, "Pipfile.lock") { 56 | return true 57 | } 58 | 59 | return false 60 | } 61 | 62 | func (b PythonBuilder) Execute() error { 63 | b.Config.HandlerMap = b.GetHandlerMap() 64 | return executeBuilder(b.script(), b.Config) 65 | } 66 | 67 | func (b PythonBuilder) GetBuildImage() string { 68 | return b.Config.BuilderBuildImage 69 | } 70 | 71 | func (b PythonBuilder) GetConfig() Config { 72 | return b.Config 73 | } 74 | 75 | func (b PythonBuilder) GetHandlerMap() map[string]string { 76 | return map[string]string{ 77 | "app.py": "app.handler", 78 | "function.py": "function.handler", 79 | "lambda_function.py": "lambda_function.handler", 80 | "main.py": "main.handler", 81 | } 82 | } 83 | 84 | func (b PythonBuilder) Name() string { 85 | return "python" 86 | } 87 | 88 | func (b PythonBuilder) script() string { 89 | return ` 90 | #!/usr/bin/env bash 91 | set -eo pipefail 92 | 93 | # Tell Python to not buffer it's stdin/stdout. 94 | export PYTHONUNBUFFERED=1 95 | 96 | indent() { 97 | sed -u "s/^/ /" 98 | } 99 | 100 | puts-step() { 101 | echo "-----> $*" 102 | } 103 | 104 | puts-warning() { 105 | echo " ! $*" 106 | } 107 | 108 | install-pip() { 109 | puts-step "Installing dependencies via pip" 110 | version="$(python-major-minor)" 111 | mkdir -p ".venv/lib/python${version}" 112 | pip install --target ".venv/lib/python${version}/site-packages" -r requirements.txt 2>&1 | indent 113 | } 114 | 115 | install-pipenv() { 116 | puts-step "Creating virtualenv" 117 | virtualenv -p python .venv | indent 118 | 119 | puts-step "Installing dependencies via pipenv" 120 | export PIPENV_VENV_IN_PROJECT=1 121 | version="$(python-major-minor)" 122 | 123 | if [[ ! -f "Pipfile.lock" ]]; then 124 | pipenv install --skip-lock 2>&1 | indent 125 | else 126 | pipenv install --deploy 2>&1 | indent 127 | fi 128 | } 129 | 130 | install-poetry() { 131 | puts-step "Installing dependencies via poetry" 132 | poetry config virtualenvs.create true 133 | poetry config virtualenvs.in-project true 134 | poetry install --no-dev 2>&1 | indent 135 | } 136 | 137 | python-major-minor() { 138 | python -c 'import sys; print(str(sys.version_info[0])+"."+str(sys.version_info[1]))' 139 | } 140 | 141 | cleanup-deps() { 142 | puts-step "Writing dependencies to correct path" 143 | version="$(python-major-minor)" 144 | 145 | find "/var/task/.venv/lib/python${version}/site-packages" -type f -print0 | xargs -0 chmod 644 146 | find "/var/task/.venv/lib/python${version}/site-packages" -type d -print0 | xargs -0 chmod 755 147 | pushd "/var/task/.venv/lib/python${version}/site-packages" >/dev/null || return 1 148 | mv --no-clobber * /var/task/ 149 | popd >/dev/null || return 1 150 | rm -rf /var/task/.venv 151 | } 152 | 153 | hook-pre-compile() { 154 | if [[ ! -f bin/pre_compile ]]; then 155 | return 156 | fi 157 | 158 | puts-step "Running pre-compile hook" 159 | chmod +x bin/pre_compile 160 | bin/pre_compile 161 | } 162 | 163 | hook-post-compile() { 164 | if [[ ! -f bin/post_compile ]]; then 165 | return 166 | fi 167 | 168 | puts-step "Running post-compile hook" 169 | chmod +x bin/post_compile 170 | bin/post_compile 171 | } 172 | 173 | hook-package() { 174 | if [[ "$LAMBDA_BUILD_ZIP" != "1" ]]; then 175 | return 176 | fi 177 | 178 | puts-step "Creating package at lambda.zip" 179 | zip -q -r lambda.zip ./* 180 | } 181 | 182 | hook-pre-compile 183 | 184 | if [[ -f "requirements.txt" ]]; then 185 | install-pip 186 | elif [[ -f "Pipfile" ]]; then 187 | install-pipenv 188 | elif [[ -f "poetry.lock" ]] || [[ -f "pyproject.toml" ]]; then 189 | install-poetry 190 | else 191 | puts-warning "No dependency file detected" 192 | exit 1 193 | fi 194 | 195 | cleanup-deps 196 | hook-post-compile 197 | hook-package 198 | ` 199 | } 200 | 201 | func parsePythonVersion(workingDirectory string, supportedPythonVersions []string) (string, error) { 202 | var err error 203 | version := "3.9" 204 | if io.FileExistsInDirectory(workingDirectory, "Pipfile.lock") { 205 | version, err = parsePythonVersionFromPipfileLock(workingDirectory) 206 | } 207 | 208 | if io.FileExistsInDirectory(workingDirectory, "poetry.lock") { 209 | version, err = parsePythonVersionFromPoetryLock(workingDirectory, supportedPythonVersions) 210 | } 211 | 212 | if io.FileExistsInDirectory(workingDirectory, "runtime.txt") { 213 | version, err = parsePythonVersionFromRuntimeTxt(workingDirectory) 214 | } 215 | 216 | if err != nil { 217 | return "", err 218 | } 219 | 220 | constraint, err := semver.NewConstraint(version) 221 | if err != nil { 222 | return "", fmt.Errorf("error parsing version python constraint: %w", err) 223 | } 224 | 225 | for _, version := range supportedPythonVersions { 226 | v, err := semver.NewVersion(version) 227 | if err != nil { 228 | return "", fmt.Errorf("error parsing supported python version '%s': %s", version, err.Error()) 229 | } 230 | 231 | if constraint.Check(v) { 232 | return fmt.Sprintf("%d.%d", v.Major(), v.Minor()), nil 233 | } 234 | } 235 | 236 | return version, err 237 | } 238 | 239 | func parsePythonVersionFromPipfileLock(workingDirectory string) (string, error) { 240 | f, err := os.Open(filepath.Join(workingDirectory, "Pipfile.lock")) 241 | if err != nil { 242 | return "", fmt.Errorf("error opening Pipefile.lock: %w", err) 243 | } 244 | defer f.Close() 245 | 246 | bytes, err := ioutil.ReadAll(f) 247 | if err != nil { 248 | return "", fmt.Errorf("error reading Pipefile.lock: %w", err) 249 | } 250 | 251 | type PipefileLock struct { 252 | Meta struct { 253 | Requires struct { 254 | PythonVersion string `json:"python_version"` 255 | } `json:"requires"` 256 | } `json:"_meta"` 257 | } 258 | var pipefileLock PipefileLock 259 | if err := json.Unmarshal(bytes, &pipefileLock); err != nil { 260 | return "", fmt.Errorf("error unmarshaling Pipfile.lock: %w", err) 261 | } 262 | 263 | if pipefileLock.Meta.Requires.PythonVersion == "" { 264 | return "3.9", nil 265 | } 266 | 267 | return pipefileLock.Meta.Requires.PythonVersion, nil 268 | } 269 | 270 | func parsePythonVersionFromPoetryLock(workingDirectory string, supportedPythonVersions []string) (string, error) { 271 | f, err := os.Open(filepath.Join(workingDirectory, "poetry.lock")) 272 | if err != nil { 273 | return "", fmt.Errorf("error opening poetry.lock: %w", err) 274 | } 275 | defer f.Close() 276 | 277 | bytes, err := ioutil.ReadAll(f) 278 | if err != nil { 279 | return "", fmt.Errorf("error reading poetry.lock: %w", err) 280 | } 281 | 282 | type PoetryLock struct { 283 | Metadata struct { 284 | PythonVersions string `toml:"python-versions"` 285 | } `toml:"metadata"` 286 | } 287 | var poetryLock PoetryLock 288 | if err := toml.Unmarshal(bytes, &poetryLock); err != nil { 289 | return "", fmt.Errorf("error unmarshaling poetry.lock: %w", err) 290 | } 291 | 292 | if poetryLock.Metadata.PythonVersions == "" || poetryLock.Metadata.PythonVersions == "*" { 293 | return "3.9", nil 294 | } 295 | 296 | if v, err := semver.NewVersion(poetryLock.Metadata.PythonVersions); err == nil { 297 | poetryLock.Metadata.PythonVersions = fmt.Sprintf("%d.%d", v.Major(), v.Minor()) 298 | } 299 | 300 | constraint, err := semver.NewConstraint(poetryLock.Metadata.PythonVersions) 301 | if err != nil { 302 | return "", fmt.Errorf("error parsing poetry.lock python constraint: %w", err) 303 | } 304 | 305 | for _, version := range supportedPythonVersions { 306 | v, err := semver.NewVersion(version) 307 | if err != nil { 308 | return "", fmt.Errorf("error parsing supported python version '%s': %s", version, err.Error()) 309 | } 310 | 311 | if constraint.Check(v) { 312 | return version, nil 313 | } 314 | } 315 | 316 | return "", fmt.Errorf("no valid python version found") 317 | } 318 | 319 | func parsePythonVersionFromRuntimeTxt(workingDirectory string) (string, error) { 320 | f, err := os.Open(filepath.Join(workingDirectory, "runtime.txt")) 321 | if err != nil { 322 | return "", fmt.Errorf("error opening runtime.txt: %w", err) 323 | } 324 | defer f.Close() 325 | 326 | bytes, err := ioutil.ReadAll(f) 327 | if err != nil { 328 | return "", fmt.Errorf("error reading runtime.txt: %w", err) 329 | } 330 | 331 | lines := strings.Split(strings.TrimSpace(string(bytes)), "\n") 332 | if len(lines) != 1 { 333 | return "", fmt.Errorf("error parsing runtime.txt, expected 1 line, found %d", len(lines)) 334 | } 335 | 336 | parts := strings.SplitN(lines[0], "-", 2) 337 | if len(parts) != 2 { 338 | return "", fmt.Errorf("error parsing runtime.txt, contents: %v", lines[0]) 339 | } 340 | 341 | v, err := semver.NewVersion(parts[1]) 342 | if err != nil { 343 | return "", fmt.Errorf("error parsing semver from runtime.txt: %w", err) 344 | } 345 | 346 | return fmt.Sprintf("%d.%d", v.Major(), v.Minor()), nil 347 | } 348 | -------------------------------------------------------------------------------- /builders/ruby.go: -------------------------------------------------------------------------------- 1 | package builders 2 | 3 | import "lambda-builder/io" 4 | 5 | type RubyBuilder struct { 6 | Config Config 7 | } 8 | 9 | func NewRubyBuilder(config Config) (RubyBuilder, error) { 10 | var err error 11 | config.BuilderBuildImage, err = getBuildImage(config, "mlupin/docker-lambda:ruby2.7-build") 12 | if err != nil { 13 | return RubyBuilder{}, err 14 | } 15 | 16 | config.BuilderRunImage, err = getRunImage(config, "mlupin/docker-lambda:ruby2.7") 17 | if err != nil { 18 | return RubyBuilder{}, err 19 | } 20 | 21 | return RubyBuilder{ 22 | Config: config, 23 | }, nil 24 | } 25 | 26 | func (b RubyBuilder) Detect() bool { 27 | if io.FileExistsInDirectory(b.Config.WorkingDirectory, "Gemfile.lock") { 28 | return true 29 | } 30 | 31 | return false 32 | } 33 | 34 | func (b RubyBuilder) GetBuildImage() string { 35 | return b.Config.BuilderBuildImage 36 | } 37 | 38 | func (b RubyBuilder) GetConfig() Config { 39 | return b.Config 40 | } 41 | 42 | func (b RubyBuilder) GetHandlerMap() map[string]string { 43 | return map[string]string{ 44 | "function.rb": "function.handler", 45 | "lambda_function.rb": "lambda_function.handler", 46 | } 47 | } 48 | 49 | func (b RubyBuilder) Execute() error { 50 | b.Config.HandlerMap = b.GetHandlerMap() 51 | return executeBuilder(b.script(), b.Config) 52 | } 53 | 54 | func (b RubyBuilder) Name() string { 55 | return "ruby" 56 | } 57 | 58 | func (b RubyBuilder) script() string { 59 | return ` 60 | #!/usr/bin/env bash 61 | set -eo pipefail 62 | 63 | [ "$BUILDER_XTRACE" ] && set -o xtrace 64 | 65 | indent() { 66 | sed -u "s/^/ /" 67 | } 68 | 69 | puts-step() { 70 | echo "-----> $*" 71 | } 72 | 73 | install-bundler() { 74 | puts-step "Downloading dependencies via bundler" 75 | bundle config set --local path 'vendor/bundle' 2>&1 | indent 76 | bundle install 2>&1 | indent 77 | } 78 | 79 | hook-pre-compile() { 80 | if [[ ! -f bin/pre_compile ]]; then 81 | return 82 | fi 83 | 84 | puts-step "Running pre-compile hook" 85 | chmod +x bin/pre_compile 86 | bin/pre_compile 87 | } 88 | 89 | hook-post-compile() { 90 | if [[ ! -f bin/post_compile ]]; then 91 | return 92 | fi 93 | 94 | puts-step "Running post-compile hook" 95 | chmod +x bin/post_compile 96 | bin/post_compile 97 | } 98 | 99 | hook-package() { 100 | if [[ "$LAMBDA_BUILD_ZIP" != "1" ]]; then 101 | return 102 | fi 103 | 104 | puts-step "Creating package at lambda.zip" 105 | zip -q -r lambda.zip ./* 106 | } 107 | 108 | hook-pre-compile 109 | install-bundler 110 | hook-post-compile 111 | hook-package 112 | ` 113 | } 114 | -------------------------------------------------------------------------------- /commands/build.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | 9 | "lambda-builder/builders" 10 | "lambda-builder/io" 11 | "lambda-builder/ui" 12 | 13 | "github.com/google/uuid" 14 | "github.com/josegonzalez/cli-skeleton/command" 15 | "github.com/posener/complete" 16 | flag "github.com/spf13/pflag" 17 | ) 18 | 19 | type BuildCommand struct { 20 | command.Meta 21 | 22 | buildEnv []string 23 | builder string 24 | buildImage string 25 | generateRunImage bool 26 | handler string 27 | imageEnv []string 28 | imageTag string 29 | labels []string 30 | port int 31 | quiet bool 32 | runImage string 33 | workingDirectory string 34 | writeProcfile bool 35 | } 36 | 37 | func (c *BuildCommand) Name() string { 38 | return "build" 39 | } 40 | 41 | func (c *BuildCommand) Synopsis() string { 42 | return "Builds a lambda function" 43 | } 44 | 45 | func (c *BuildCommand) Help() string { 46 | return command.CommandHelp(c) 47 | } 48 | 49 | func (c *BuildCommand) Examples() map[string]string { 50 | appName := os.Getenv("CLI_APP_NAME") 51 | return map[string]string{ 52 | "Builds a lambda.zip for the current directory": fmt.Sprintf("%s %s", appName, c.Name()), 53 | } 54 | } 55 | 56 | func (c *BuildCommand) Arguments() []command.Argument { 57 | args := []command.Argument{} 58 | return args 59 | } 60 | 61 | func (c *BuildCommand) AutocompleteArgs() complete.Predictor { 62 | return complete.PredictNothing 63 | } 64 | 65 | func (c *BuildCommand) ParsedArguments(args []string) (map[string]command.Argument, error) { 66 | return command.ParseArguments(args, c.Arguments()) 67 | } 68 | 69 | func (c *BuildCommand) FlagSet() *flag.FlagSet { 70 | workingDirectory, err := os.Getwd() 71 | if err != nil { 72 | panic(err) 73 | } 74 | 75 | f := c.Meta.FlagSet(c.Name(), command.FlagSetClient) 76 | f.BoolVar(&c.generateRunImage, "generate-image", false, "build a docker image") 77 | f.BoolVar(&c.quiet, "quiet", false, "run builder in quiet mode") 78 | f.BoolVar(&c.writeProcfile, "write-procfile", false, "writes a Procfile if a handler is specified or detected") 79 | f.IntVar(&c.port, "port", -1, "set the default port for the lambda to listen on") 80 | f.StringVar(&c.builder, "builder", "", "set the builder to use") 81 | f.StringVar(&c.buildImage, "build-image", "", "set the build-image to use") 82 | f.StringVar(&c.handler, "handler", "", "handler override to specify as the default command to run in a built image") 83 | f.StringVar(&c.runImage, "run-image", "", "set the run-image to use") 84 | f.StringVar(&c.workingDirectory, "working-directory", workingDirectory, "working directory") 85 | f.StringVarP(&c.imageTag, "tag", "t", "", "name and optionally a tag in the 'name:tag' format") 86 | f.StringArrayVar(&c.buildEnv, "build-env", []string{}, "environment variables to be set for the build context") 87 | f.StringArrayVar(&c.imageEnv, "image-env", []string{}, "environment variables to be committed to a built image") 88 | f.StringArrayVar(&c.labels, "label", []string{}, "set metadata for an image") 89 | return f 90 | } 91 | 92 | func (c *BuildCommand) AutocompleteFlags() complete.Flags { 93 | return command.MergeAutocompleteFlags( 94 | c.Meta.AutocompleteFlags(command.FlagSetClient), 95 | complete.Flags{ 96 | "--build-env": complete.PredictAnything, 97 | "--build-image": complete.PredictAnything, 98 | "--builder": complete.PredictSet("dotnet", "go", "nodejs", "python", "ruby"), 99 | "--generate-image": complete.PredictNothing, 100 | "--handler": complete.PredictAnything, 101 | "--image-env": complete.PredictAnything, 102 | "--label": complete.PredictAnything, 103 | "--port": complete.PredictAnything, 104 | "--quiet": complete.PredictNothing, 105 | "--run-image": complete.PredictAnything, 106 | "-t": complete.PredictAnything, 107 | "--tag": complete.PredictAnything, 108 | "--working-directory": complete.PredictAnything, 109 | "--write-procfile": complete.PredictNothing, 110 | }, 111 | ) 112 | } 113 | 114 | func (c *BuildCommand) Run(args []string) int { 115 | flags := c.FlagSet() 116 | flags.Usage = func() { c.Ui.Output(c.Help()) } 117 | if err := flags.Parse(args); err != nil { 118 | c.Ui.Error(err.Error()) 119 | c.Ui.Error(command.CommandErrorText(c)) 120 | return 1 121 | } 122 | 123 | var err error 124 | c.workingDirectory, err = filepath.Abs(c.workingDirectory) 125 | if err != nil { 126 | c.Ui.Error(err.Error()) 127 | return 1 128 | } 129 | 130 | logger, ok := c.Ui.(*ui.ZerologUi) 131 | if !ok { 132 | c.Ui.Error("Unable to fetch logger from cli") 133 | return 1 134 | } 135 | 136 | if !io.FolderExists(c.workingDirectory) { 137 | c.Ui.Error(fmt.Sprintf("Working directory '%s' does not exist", c.workingDirectory)) 138 | return 1 139 | } 140 | 141 | if io.FileExistsInDirectory(c.workingDirectory, "lambda.zip") { 142 | c.Ui.Warn("Removing existing lambda.zip from working directory") 143 | os.Remove(filepath.Join(c.workingDirectory, "lambda.zip")) 144 | } 145 | 146 | identifier := uuid.New().String() 147 | config := builders.Config{ 148 | BuildEnv: c.buildEnv, 149 | Builder: c.builder, 150 | BuilderBuildImage: c.buildImage, 151 | BuilderRunImage: c.runImage, 152 | GenerateRunImage: c.generateRunImage, 153 | Identifier: identifier, 154 | ImageEnv: c.imageEnv, 155 | ImageLabels: c.labels, 156 | ImageTag: c.imageTag, 157 | Port: c.port, 158 | RunQuiet: c.quiet, 159 | WorkingDirectory: c.workingDirectory, 160 | WriteProcfile: c.writeProcfile, 161 | } 162 | 163 | logger.LogHeader1("Detecting builder") 164 | builder, err := detectBuilder(config) 165 | if err != nil { 166 | c.Ui.Error(err.Error()) 167 | return 1 168 | } 169 | 170 | c.Ui.Info(fmt.Sprintf("Detected %s builder", builder.Name())) 171 | 172 | logger.LogHeader1(fmt.Sprintf("Building app with image %s", builder.GetBuildImage())) 173 | if err := builder.Execute(); err != nil { 174 | c.Ui.Error(err.Error()) 175 | return 1 176 | } 177 | 178 | zipPath := filepath.Join(c.workingDirectory, "lambda.zip") 179 | logger.LogHeader1(fmt.Sprintf("Wrote %s", zipPath)) 180 | sizeInBytes, err := io.FileSize(zipPath) 181 | if err != nil { 182 | c.Ui.Error(fmt.Sprintf("Error getting filesize for %s: %s", zipPath, err.Error())) 183 | return 1 184 | } 185 | 186 | sizeInKB := io.BytesToKilobytes(sizeInBytes) 187 | sizeInMB := io.BytesToMegabytes(sizeInBytes) 188 | if sizeInMB >= 50 { 189 | c.Ui.Warn(fmt.Sprintf("Surpassed AWS Lambda 50MB zip file limit: %dMB (%dKB)", sizeInMB, sizeInKB)) 190 | c.Ui.Warn("Consider using Docker Images for lambda function distribution") 191 | } else { 192 | c.Ui.Info(fmt.Sprintf("Current zip file size: %dMB (%dKB)", sizeInMB, sizeInKB)) 193 | } 194 | 195 | return 0 196 | } 197 | 198 | func detectBuilder(config builders.Config) (builders.Builder, error) { 199 | var builder builders.Builder 200 | var err error 201 | bs := []builders.Builder{} 202 | 203 | lambdaYML, err := builders.ParseLambdaYML(config) 204 | if err != nil { 205 | return nil, err 206 | } 207 | 208 | builder, err = builders.NewDotnetBuilder(config) 209 | if err != nil { 210 | return nil, err 211 | } 212 | bs = append(bs, builder) 213 | 214 | builder, err = builders.NewGoBuilder(config) 215 | if err != nil { 216 | return nil, err 217 | } 218 | bs = append(bs, builder) 219 | 220 | builder, err = builders.NewNodejsBuilder(config) 221 | if err != nil { 222 | return nil, err 223 | } 224 | bs = append(bs, builder) 225 | 226 | builder, err = builders.NewPythonBuilder(config) 227 | if err != nil { 228 | return nil, err 229 | } 230 | bs = append(bs, builder) 231 | 232 | builder, err = builders.NewRubyBuilder(config) 233 | if err != nil { 234 | return nil, err 235 | } 236 | bs = append(bs, builder) 237 | 238 | selectedImage := lambdaYML.Builder 239 | if config.Builder != "" { 240 | selectedImage = config.Builder 241 | } 242 | for _, builder := range bs { 243 | if selectedImage != "" && selectedImage != builder.Name() { 244 | continue 245 | } 246 | 247 | if builder.Detect() { 248 | return builder, nil 249 | } 250 | } 251 | 252 | return nil, errors.New("no builder detected") 253 | } 254 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module lambda-builder 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.2 6 | 7 | require ( 8 | github.com/BurntSushi/toml v1.5.0 9 | github.com/Masterminds/semver v1.5.0 10 | github.com/alexellis/go-execute v0.6.0 11 | github.com/aws/aws-lambda-go v1.49.0 12 | github.com/codeclysm/extract/v4 v4.0.0 13 | github.com/google/uuid v1.6.0 14 | github.com/josegonzalez/cli-skeleton v0.22.0 15 | github.com/mattn/go-isatty v0.0.20 16 | github.com/mitchellh/cli v1.1.5 17 | github.com/posener/complete v1.2.3 18 | github.com/rs/zerolog v1.34.0 19 | github.com/spf13/pflag v1.0.6 20 | gopkg.in/yaml.v2 v2.4.0 21 | ) 22 | 23 | require ( 24 | github.com/Masterminds/goutils v1.1.1 // indirect 25 | github.com/Masterminds/semver/v3 v3.1.1 // indirect 26 | github.com/Masterminds/sprig/v3 v3.2.2 // indirect 27 | github.com/armon/go-radix v1.0.0 // indirect 28 | github.com/bgentry/speakeasy v0.1.0 // indirect 29 | github.com/fatih/color v1.13.0 // indirect 30 | github.com/h2non/filetype v1.1.3 // indirect 31 | github.com/hashicorp/errwrap v1.1.0 // indirect 32 | github.com/hashicorp/go-multierror v1.1.1 // indirect 33 | github.com/huandu/xstrings v1.3.2 // indirect 34 | github.com/imdario/mergo v0.3.13 // indirect 35 | github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 // indirect 36 | github.com/juju/loggo v1.0.0 // indirect 37 | github.com/klauspost/compress v1.15.13 // indirect 38 | github.com/mattn/go-colorable v0.1.14 // indirect 39 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect 40 | github.com/mitchellh/copystructure v1.2.0 // indirect 41 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 42 | github.com/shopspring/decimal v1.3.1 // indirect 43 | github.com/spf13/cast v1.5.0 // indirect 44 | github.com/ulikunitz/xz v0.5.12 // indirect 45 | golang.org/x/crypto v0.36.0 // indirect 46 | golang.org/x/sys v0.31.0 // indirect 47 | golang.org/x/term v0.30.0 // indirect 48 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect 49 | ) 50 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= 2 | github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 3 | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= 4 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 5 | github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= 6 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 7 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= 8 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 9 | github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= 10 | github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= 11 | github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= 12 | github.com/alexellis/go-execute v0.6.0 h1:FVGoudJnWSObwf9qmehbvVuvhK6g1UpKOCBjS+OUXEA= 13 | github.com/alexellis/go-execute v0.6.0/go.mod h1:nlg2F6XdYydUm1xXQMMiuibQCV1mveybBkNWfdNznjk= 14 | github.com/arduino/go-paths-helper v1.12.1 h1:WkxiVUxBjKWlLMiMuYy8DcmVrkxdP7aKxQOAq7r2lVM= 15 | github.com/arduino/go-paths-helper v1.12.1/go.mod h1:jcpW4wr0u69GlXhTYydsdsqAjLaYK5n7oWHfKqOG6LM= 16 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 17 | github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= 18 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 19 | github.com/aws/aws-lambda-go v1.49.0 h1:z4VhTqkFZPM3xpEtTqWqRqsRH4TZBMJqTkRiBPYLqIQ= 20 | github.com/aws/aws-lambda-go v1.49.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= 21 | github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= 22 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 23 | github.com/codeclysm/extract/v4 v4.0.0 h1:H87LFsUNaJTu2e/8p/oiuiUsOK/TaPQ5wxsjPnwPEIY= 24 | github.com/codeclysm/extract/v4 v4.0.0/go.mod h1:SFju1lj6as7FvUgalpSct7torJE0zttbJUWtryPRG6s= 25 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 26 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 27 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 28 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 30 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 31 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 32 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 33 | github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= 34 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 35 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 36 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 37 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 38 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 39 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 40 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 41 | github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= 42 | github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= 43 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 44 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 45 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 46 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 47 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 48 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 49 | github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 50 | github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= 51 | github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 52 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 53 | github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= 54 | github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= 55 | github.com/josegonzalez/cli-skeleton v0.22.0 h1:mmL4WkvAgG8xtJq8FNKWLv91EH3pXn6YRfKjSQi1MKE= 56 | github.com/josegonzalez/cli-skeleton v0.22.0/go.mod h1:Y2gV1FjsTKUTvNw1koCxEMSkSlx82nzaOWT1R1BSg6Y= 57 | github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= 58 | github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 h1:rhqTjzJlm7EbkELJDKMTU7udov+Se0xZkWmugr6zGok= 59 | github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= 60 | github.com/juju/loggo v1.0.0 h1:Y6ZMQOGR9Aj3BGkiWx7HBbIx6zNwNkxhVNOHU2i1bl0= 61 | github.com/juju/loggo v1.0.0/go.mod h1:NIXFioti1SmKAlKNuUwbMenNdef59IF52+ZzuOmHYkg= 62 | github.com/juju/testing v0.0.0-20200510222523-6c8c298c77a0 h1:+WWUkhnTjV6RNOxkcwk79qrjeyHEHvBzlneueBsatX4= 63 | github.com/juju/testing v0.0.0-20200510222523-6c8c298c77a0/go.mod h1:hpGvhGHPVbNBraRLZEhoQwFLMrjK8PSlO4D3nDjKYXo= 64 | github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0= 65 | github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= 66 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 67 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 68 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 69 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 70 | github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= 71 | github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 72 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 73 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 74 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 75 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 76 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 77 | github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 78 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 79 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 80 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 81 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 82 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 83 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 84 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 85 | github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= 86 | github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= 87 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= 88 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= 89 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 90 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 91 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 92 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 93 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 94 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 95 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 96 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 97 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 98 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 99 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 100 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 101 | github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= 102 | github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= 103 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 104 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 105 | github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= 106 | github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= 107 | github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= 108 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 109 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= 110 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 111 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 112 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= 113 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 114 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 115 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 116 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 117 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 118 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 119 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 120 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 121 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 122 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 123 | github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= 124 | github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 125 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 126 | golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 127 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 128 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 129 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 130 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 131 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 132 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 133 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 134 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 135 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 136 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 137 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 138 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 139 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 140 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 141 | golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= 142 | golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 143 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 144 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 145 | gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 146 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 147 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 148 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= 149 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= 150 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 151 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 152 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 153 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 154 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 155 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 156 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 157 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 158 | -------------------------------------------------------------------------------- /io/main.go: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | func FileExistsInDirectory(directory string, filename string) bool { 9 | if _, err := os.Stat(filepath.Join(directory, filename)); err == nil { 10 | return true 11 | } 12 | return false 13 | } 14 | 15 | func FolderExists(directory string) bool { 16 | info, err := os.Stat(directory) 17 | if err != nil { 18 | return false 19 | } 20 | 21 | return info.IsDir() 22 | } 23 | 24 | func FileSize(filepath string) (int64, error) { 25 | file, err := os.Open(filepath) 26 | if err != nil { 27 | return 0, err 28 | } 29 | defer file.Close() 30 | 31 | stat, err := file.Stat() 32 | if err != nil { 33 | return 0, err 34 | } 35 | 36 | return stat.Size(), nil 37 | } 38 | 39 | func BytesToKilobytes(size int64) int64 { 40 | var kilobytes int64 41 | kilobytes = (size / 1024) 42 | return kilobytes 43 | } 44 | 45 | func BytesToMegabytes(size int64) int64 { 46 | var kilobytes int64 47 | kilobytes = (size / 1024) 48 | 49 | var megabytes int64 50 | megabytes = (kilobytes / 1024) 51 | return megabytes 52 | } 53 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "lambda-builder/commands" 9 | "lambda-builder/ui" 10 | 11 | "github.com/josegonzalez/cli-skeleton/command" 12 | "github.com/mitchellh/cli" 13 | ) 14 | 15 | // The name of the cli tool 16 | var AppName = "lambda-builder" 17 | 18 | // Holds the version 19 | var Version string 20 | 21 | func main() { 22 | os.Exit(Run(os.Args[1:])) 23 | } 24 | 25 | // Executes the specified subcommand 26 | func Run(args []string) int { 27 | ctx := context.Background() 28 | commandMeta := command.SetupRun(ctx, AppName, Version, args) 29 | commandMeta.Ui = ui.ZerologUiWithFields(commandMeta.Ui, make(map[string]interface{}, 0)) 30 | c := cli.NewCLI(AppName, Version) 31 | c.Args = os.Args[1:] 32 | c.Commands = command.Commands(ctx, commandMeta, Commands) 33 | exitCode, err := c.Run() 34 | if err != nil { 35 | fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error()) 36 | return 1 37 | } 38 | 39 | return exitCode 40 | } 41 | 42 | // Returns a list of implemented commands 43 | func Commands(ctx context.Context, meta command.Meta) map[string]cli.CommandFactory { 44 | return map[string]cli.CommandFactory{ 45 | "build": func() (cli.Command, error) { 46 | return &commands.BuildCommand{Meta: meta}, nil 47 | }, 48 | "version": func() (cli.Command, error) { 49 | return &command.VersionCommand{Meta: meta}, nil 50 | }, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | export SYSTEM_NAME="$(uname -s | tr '[:upper:]' '[:lower:]')" 4 | export LAMBDA_BUILDER_BIN="build/$SYSTEM_NAME/lambda-builder-amd64" 5 | 6 | setup_file() { 7 | make prebuild "$LAMBDA_BUILDER_BIN" 8 | } 9 | 10 | teardown_file() { 11 | make clean 12 | } 13 | 14 | @test "[build] write procfile" { 15 | skip "This test does not run correctly in Github Actions due to use of embedded docker" 16 | run $LAMBDA_BUILDER_BIN build --working-directory tests/go --write-procfile 17 | echo "output: $output" 18 | echo "status: $status" 19 | [[ "$status" -eq 0 ]] 20 | [[ -f tests/go/Procfile ]] 21 | } 22 | 23 | @test "[build] dotnet6" { 24 | run $LAMBDA_BUILDER_BIN build --working-directory tests/dotnet6 25 | echo "output: $output" 26 | echo "status: $status" 27 | [[ "$status" -eq 0 ]] 28 | } 29 | 30 | @test "[build] go" { 31 | run $LAMBDA_BUILDER_BIN build --working-directory tests/go 32 | echo "output: $output" 33 | echo "status: $status" 34 | [[ "$status" -eq 0 ]] 35 | } 36 | 37 | @test "[build] go without go modules" { 38 | run $LAMBDA_BUILDER_BIN build --working-directory tests/go-nomod 39 | echo "output: $output" 40 | echo "status: $status" 41 | [[ "$status" -eq 0 ]] 42 | } 43 | 44 | @test "[build] hooks" { 45 | run $LAMBDA_BUILDER_BIN build --working-directory tests/hooks 46 | echo "output: $output" 47 | echo "status: $status" 48 | [[ "$status" -eq 0 ]] 49 | } 50 | 51 | @test "[build] lambda.yml" { 52 | run $LAMBDA_BUILDER_BIN build --working-directory tests/lambda.yml 53 | echo "output: $output" 54 | echo "status: $status" 55 | [[ "$status" -eq 0 ]] 56 | } 57 | 58 | @test "[build] lambda.yml-invalid-image" { 59 | run $LAMBDA_BUILDER_BIN build --working-directory tests/lambda.yml-invalid-image 60 | echo "output: $output" 61 | echo "status: $status" 62 | [[ "$status" -eq 1 ]] 63 | } 64 | 65 | @test "[build] lambda.yml-nonexistent-builder" { 66 | run $LAMBDA_BUILDER_BIN build --working-directory tests/lambda.yml-nonexistent-builder 67 | echo "output: $output" 68 | echo "status: $status" 69 | [[ "$status" -eq 1 ]] 70 | } 71 | 72 | @test "[build] npm" { 73 | run $LAMBDA_BUILDER_BIN build --working-directory tests/npm 74 | echo "output: $output" 75 | echo "status: $status" 76 | [[ "$status" -eq 0 ]] 77 | } 78 | 79 | @test "[build] nonexistent" { 80 | run $LAMBDA_BUILDER_BIN build --working-directory tests/nonexistent 81 | echo "output: $output" 82 | echo "status: $status" 83 | [[ "$status" -eq 1 ]] 84 | } 85 | 86 | @test "[build] non-detected" { 87 | run $LAMBDA_BUILDER_BIN build --working-directory tests/non-detected 88 | echo "output: $output" 89 | echo "status: $status" 90 | [[ "$status" -eq 1 ]] 91 | } 92 | 93 | @test "[build] pip" { 94 | run $LAMBDA_BUILDER_BIN build --working-directory tests/pip 95 | echo "output: $output" 96 | echo "status: $status" 97 | [[ "$status" -eq 0 ]] 98 | } 99 | 100 | @test "[build] pip-runtime" { 101 | run $LAMBDA_BUILDER_BIN build --working-directory tests/pip-runtime 102 | echo "output: $output" 103 | echo "status: $status" 104 | [[ "$status" -eq 0 ]] 105 | } 106 | 107 | @test "[build] pipenv" { 108 | run $LAMBDA_BUILDER_BIN build --working-directory tests/pipenv 109 | echo "output: $output" 110 | echo "status: $status" 111 | [[ "$status" -eq 0 ]] 112 | } 113 | 114 | @test "[build] poetry" { 115 | run $LAMBDA_BUILDER_BIN build --working-directory tests/poetry 116 | echo "output: $output" 117 | echo "status: $status" 118 | [[ "$status" -eq 0 ]] 119 | } 120 | 121 | @test "[build] ruby" { 122 | run $LAMBDA_BUILDER_BIN build --working-directory tests/ruby 123 | echo "output: $output" 124 | echo "status: $status" 125 | [[ "$status" -eq 0 ]] 126 | } 127 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Test Apps 2 | 3 | This directory contains test apps for use with lambda-builder. 4 | 5 | Where possible, there is a `test.bats` file within the test app. This file will: 6 | 7 | - build the lambda (using `lambda-builder` on the `$PATH`) 8 | - create the iam role and attach the managed `AWSLambdaBasicExecutionRole` role 9 | - create a lambda function with the proper runtime (as expected by lambda-builder) with the aforementioned iam role 10 | - invoke the lambda function with a minimal payload 11 | - except for Cloudwatch Log Groups, cleanup after itself when complete 12 | 13 | Please be aware that all resources created will have the prefix `lambda-` and be tagged with the following tags: 14 | 15 | - `app=lambda-builder` 16 | - `com.dokku.lambda-builder/runtime=$LAMBDA_RUNTIME` 17 | -------------------------------------------------------------------------------- /tests/dotnet6/Function.cs: -------------------------------------------------------------------------------- 1 | // Compile with: 2 | // docker run --rm -v "$PWD":/var/task mlupin/docker-lambda:build-dotnet6 dotnet publish -c Release -o pub 3 | 4 | // Run with: 5 | // docker run --rm -v "$PWD"/pub:/var/task mlupin/docker-lambda:dotnet6 test::test.Function::FunctionHandler '{"some": "event"}' 6 | 7 | using System; 8 | using System.Collections; 9 | using Amazon.Lambda.Core; 10 | 11 | [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] 12 | 13 | namespace test 14 | { 15 | public class Function 16 | { 17 | public string FunctionHandler(object inputEvent, ILambdaContext context) 18 | { 19 | Console.WriteLine($"inputEvent: {inputEvent}"); 20 | Console.WriteLine($"RemainingTime: {context.RemainingTime}"); 21 | 22 | foreach (DictionaryEntry kv in Environment.GetEnvironmentVariables()) 23 | { 24 | Console.WriteLine($"{kv.Key}={kv.Value}"); 25 | } 26 | 27 | return "Hello World!"; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /tests/dotnet6/test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | export LAMBDA_ROLE="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 4 | export AWS_ACCOUNT_ID="$(aws sts get-caller-identity | jq -r ".Account")" 5 | export LAMBDA_FUNCTION_NAME=lambda-dotnet6 6 | export LAMBDA_RUNTIME=dotnet6 7 | export LAMBDA_HANDLER=test::test.Function::FunctionHandler 8 | 9 | setup() { 10 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 11 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 12 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 13 | } 14 | 15 | teardown() { 16 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 17 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 18 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 19 | } 20 | 21 | @test "aws test" { 22 | run /bin/bash -c "lambda-builder build" 23 | echo "output: $output" 24 | echo "status: $status" 25 | [[ "$status" -eq 0 ]] 26 | 27 | run /bin/bash -c "aws iam create-role --role-name '$LAMBDA_FUNCTION_NAME' --tags 'Key=app,Value=lambda-builder' --tags 'Key=com.dokku.lambda-builder/runtime,Value=$LAMBDA_RUNTIME' --assume-role-policy-document '{\"Version\": \"2012-10-17\", \"Statement\": [{ \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"lambda.amazonaws.com\"}, \"Action\": \"sts:AssumeRole\"}]}'" 28 | echo "output: $output" 29 | echo "status: $status" 30 | [[ "$status" -eq 0 ]] 31 | 32 | run /bin/bash -c "aws iam attach-role-policy --role-name '$LAMBDA_FUNCTION_NAME' --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 33 | echo "output: $output" 34 | echo "status: $status" 35 | [[ "$status" -eq 0 ]] 36 | 37 | run /bin/bash -c "sleep 10" 38 | echo "output: $output" 39 | echo "status: $status" 40 | [[ "$status" -eq 0 ]] 41 | 42 | run /bin/bash -c "aws lambda create-function --function-name '$LAMBDA_FUNCTION_NAME' --package-type Zip --tags 'app=lambda-builder,com.dokku.lambda-builder/runtime=$LAMBDA_RUNTIME' --role 'arn:aws:iam::${AWS_ACCOUNT_ID}:role/$LAMBDA_FUNCTION_NAME' --zip-file fileb://lambda.zip --runtime '$LAMBDA_RUNTIME' --handler '$LAMBDA_HANDLER'" 43 | echo "output: $output" 44 | echo "status: $status" 45 | [[ "$status" -eq 0 ]] 46 | 47 | run /bin/bash -c "sleep 10" 48 | echo "output: $output" 49 | echo "status: $status" 50 | [[ "$status" -eq 0 ]] 51 | 52 | run /bin/bash -c "aws lambda get-function --function-name '$LAMBDA_FUNCTION_NAME'" 53 | echo "output: $output" 54 | echo "status: $status" 55 | [[ "$status" -eq 0 ]] 56 | 57 | run /bin/bash -c "aws lambda invoke --cli-binary-format raw-in-base64-out --function-name '$LAMBDA_FUNCTION_NAME' --payload '{\"name\": \"World\"}' response.json" 58 | echo "output: $output" 59 | echo "status: $status" 60 | [[ "$status" -eq 0 ]] 61 | } 62 | -------------------------------------------------------------------------------- /tests/dotnet6/test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/dotnet6/test.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test", "test.csproj", "{0A83D120-2336-4F30-86F1-DC045C3C9B90}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | EndGlobal -------------------------------------------------------------------------------- /tests/go-nomod/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aws/aws-lambda-go/lambda" 8 | ) 9 | 10 | type MyEvent struct { 11 | Name string `json:"name"` 12 | } 13 | 14 | func HandleRequest(ctx context.Context, name MyEvent) (string, error) { 15 | return fmt.Sprintf("Hello %s!", name.Name), nil 16 | } 17 | 18 | func main() { 19 | lambda.Start(HandleRequest) 20 | } 21 | -------------------------------------------------------------------------------- /tests/go-nomod/test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | export LAMBDA_ROLE="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 4 | export AWS_ACCOUNT_ID="$(aws sts get-caller-identity | jq -r ".Account")" 5 | export LAMBDA_FUNCTION_NAME=lambda-go1x 6 | export LAMBDA_RUNTIME=provided.al2 7 | export LAMBDA_HANDLER=bootstrap 8 | 9 | setup() { 10 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 11 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 12 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 13 | } 14 | 15 | teardown() { 16 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 17 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 18 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 19 | } 20 | 21 | @test "aws test" { 22 | run /bin/bash -c "lambda-builder build" 23 | echo "output: $output" 24 | echo "status: $status" 25 | [[ "$status" -eq 0 ]] 26 | 27 | run /bin/bash -c "aws iam create-role --role-name '$LAMBDA_FUNCTION_NAME' --tags 'Key=app,Value=lambda-builder' --tags 'Key=com.dokku.lambda-builder/runtime,Value=$LAMBDA_RUNTIME' --assume-role-policy-document '{\"Version\": \"2012-10-17\", \"Statement\": [{ \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"lambda.amazonaws.com\"}, \"Action\": \"sts:AssumeRole\"}]}'" 28 | echo "output: $output" 29 | echo "status: $status" 30 | [[ "$status" -eq 0 ]] 31 | 32 | run /bin/bash -c "aws iam attach-role-policy --role-name '$LAMBDA_FUNCTION_NAME' --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 33 | echo "output: $output" 34 | echo "status: $status" 35 | [[ "$status" -eq 0 ]] 36 | 37 | run /bin/bash -c "sleep 10" 38 | echo "output: $output" 39 | echo "status: $status" 40 | [[ "$status" -eq 0 ]] 41 | 42 | run /bin/bash -c "aws lambda create-function --function-name '$LAMBDA_FUNCTION_NAME' --package-type Zip --tags 'app=lambda-builder,com.dokku.lambda-builder/runtime=$LAMBDA_RUNTIME' --role 'arn:aws:iam::${AWS_ACCOUNT_ID}:role/$LAMBDA_FUNCTION_NAME' --zip-file fileb://lambda.zip --runtime '$LAMBDA_RUNTIME' --handler '$LAMBDA_HANDLER'" 43 | echo "output: $output" 44 | echo "status: $status" 45 | [[ "$status" -eq 0 ]] 46 | 47 | run /bin/bash -c "sleep 10" 48 | echo "output: $output" 49 | echo "status: $status" 50 | [[ "$status" -eq 0 ]] 51 | 52 | run /bin/bash -c "aws lambda get-function --function-name '$LAMBDA_FUNCTION_NAME'" 53 | echo "output: $output" 54 | echo "status: $status" 55 | [[ "$status" -eq 0 ]] 56 | 57 | run /bin/bash -c "aws lambda invoke --cli-binary-format raw-in-base64-out --function-name '$LAMBDA_FUNCTION_NAME' --payload '{\"name\": \"World\"}' response.json" 58 | echo "output: $output" 59 | echo "status: $status" 60 | [[ "$status" -eq 0 ]] 61 | } 62 | -------------------------------------------------------------------------------- /tests/go/go.mod: -------------------------------------------------------------------------------- 1 | module app 2 | 3 | go 1.22 4 | 5 | require github.com/aws/aws-lambda-go v1.29.0 6 | -------------------------------------------------------------------------------- /tests/go/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-lambda-go v1.29.0 h1:u+sfZkvNBUgt0ZkO8Q/jOMBV22DqMDMbZu04oomM2no= 2 | github.com/aws/aws-lambda-go v1.29.0/go.mod h1:aakqVz9vDHhtbt0U2zegh/z9SI2+rJ+yRREZYNQLmWY= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 8 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 9 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= 10 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 11 | -------------------------------------------------------------------------------- /tests/go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aws/aws-lambda-go/lambda" 8 | ) 9 | 10 | type MyEvent struct { 11 | Name string `json:"name"` 12 | } 13 | 14 | func HandleRequest(ctx context.Context, name MyEvent) (string, error) { 15 | return fmt.Sprintf("Hello %s!", name.Name), nil 16 | } 17 | 18 | func main() { 19 | lambda.Start(HandleRequest) 20 | } 21 | -------------------------------------------------------------------------------- /tests/go/test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | export LAMBDA_ROLE="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 4 | export AWS_ACCOUNT_ID="$(aws sts get-caller-identity | jq -r ".Account")" 5 | export LAMBDA_FUNCTION_NAME=lambda-go1x 6 | export LAMBDA_RUNTIME=provided.al2 7 | export LAMBDA_HANDLER=bootstrap 8 | 9 | setup() { 10 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 11 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 12 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 13 | } 14 | 15 | teardown() { 16 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 17 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 18 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 19 | } 20 | 21 | @test "aws test" { 22 | run /bin/bash -c "lambda-builder build" 23 | echo "output: $output" 24 | echo "status: $status" 25 | [[ "$status" -eq 0 ]] 26 | 27 | run /bin/bash -c "aws iam create-role --role-name '$LAMBDA_FUNCTION_NAME' --tags 'Key=app,Value=lambda-builder' --tags 'Key=com.dokku.lambda-builder/runtime,Value=$LAMBDA_RUNTIME' --assume-role-policy-document '{\"Version\": \"2012-10-17\", \"Statement\": [{ \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"lambda.amazonaws.com\"}, \"Action\": \"sts:AssumeRole\"}]}'" 28 | echo "output: $output" 29 | echo "status: $status" 30 | [[ "$status" -eq 0 ]] 31 | 32 | run /bin/bash -c "aws iam attach-role-policy --role-name '$LAMBDA_FUNCTION_NAME' --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 33 | echo "output: $output" 34 | echo "status: $status" 35 | [[ "$status" -eq 0 ]] 36 | 37 | run /bin/bash -c "sleep 10" 38 | echo "output: $output" 39 | echo "status: $status" 40 | [[ "$status" -eq 0 ]] 41 | 42 | run /bin/bash -c "aws lambda create-function --function-name '$LAMBDA_FUNCTION_NAME' --package-type Zip --tags 'app=lambda-builder,com.dokku.lambda-builder/runtime=$LAMBDA_RUNTIME' --role 'arn:aws:iam::${AWS_ACCOUNT_ID}:role/$LAMBDA_FUNCTION_NAME' --zip-file fileb://lambda.zip --runtime '$LAMBDA_RUNTIME' --handler '$LAMBDA_HANDLER'" 43 | echo "output: $output" 44 | echo "status: $status" 45 | [[ "$status" -eq 0 ]] 46 | 47 | run /bin/bash -c "sleep 10" 48 | echo "output: $output" 49 | echo "status: $status" 50 | [[ "$status" -eq 0 ]] 51 | 52 | run /bin/bash -c "aws lambda get-function --function-name '$LAMBDA_FUNCTION_NAME'" 53 | echo "output: $output" 54 | echo "status: $status" 55 | [[ "$status" -eq 0 ]] 56 | 57 | run /bin/bash -c "aws lambda invoke --cli-binary-format raw-in-base64-out --function-name '$LAMBDA_FUNCTION_NAME' --payload '{\"name\": \"World\"}' response.json" 58 | echo "output: $output" 59 | echo "status: $status" 60 | [[ "$status" -eq 0 ]] 61 | } 62 | -------------------------------------------------------------------------------- /tests/hooks/bin/post_compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "running post_compile" 4 | -------------------------------------------------------------------------------- /tests/hooks/bin/pre_compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "running pre_compile" 4 | -------------------------------------------------------------------------------- /tests/hooks/function.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def handler(event, context): 5 | response = requests.get("https://example.com") 6 | print(response.text) 7 | return "Hello World!" 8 | -------------------------------------------------------------------------------- /tests/hooks/requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.32.2 -------------------------------------------------------------------------------- /tests/hooks/test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | export LAMBDA_ROLE="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 4 | export AWS_ACCOUNT_ID="$(aws sts get-caller-identity | jq -r ".Account")" 5 | export LAMBDA_FUNCTION_NAME=lambda-python39-hooks 6 | export LAMBDA_RUNTIME=python3.8 7 | export LAMBDA_HANDLER=function.handler 8 | 9 | setup() { 10 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 11 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 12 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 13 | } 14 | 15 | teardown() { 16 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 17 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 18 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 19 | } 20 | 21 | @test "aws test" { 22 | run /bin/bash -c "lambda-builder build" 23 | echo "output: $output" 24 | echo "status: $status" 25 | [[ "$status" -eq 0 ]] 26 | 27 | run /bin/bash -c "aws iam create-role --role-name '$LAMBDA_FUNCTION_NAME' --tags 'Key=app,Value=lambda-builder' --tags 'Key=com.dokku.lambda-builder/runtime,Value=$LAMBDA_RUNTIME' --assume-role-policy-document '{\"Version\": \"2012-10-17\", \"Statement\": [{ \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"lambda.amazonaws.com\"}, \"Action\": \"sts:AssumeRole\"}]}'" 28 | echo "output: $output" 29 | echo "status: $status" 30 | [[ "$status" -eq 0 ]] 31 | 32 | run /bin/bash -c "aws iam attach-role-policy --role-name '$LAMBDA_FUNCTION_NAME' --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 33 | echo "output: $output" 34 | echo "status: $status" 35 | [[ "$status" -eq 0 ]] 36 | 37 | run /bin/bash -c "sleep 10" 38 | echo "output: $output" 39 | echo "status: $status" 40 | [[ "$status" -eq 0 ]] 41 | 42 | run /bin/bash -c "aws lambda create-function --function-name '$LAMBDA_FUNCTION_NAME' --package-type Zip --tags 'app=lambda-builder,com.dokku.lambda-builder/runtime=$LAMBDA_RUNTIME' --role 'arn:aws:iam::${AWS_ACCOUNT_ID}:role/$LAMBDA_FUNCTION_NAME' --zip-file fileb://lambda.zip --runtime '$LAMBDA_RUNTIME' --handler '$LAMBDA_HANDLER'" 43 | echo "output: $output" 44 | echo "status: $status" 45 | [[ "$status" -eq 0 ]] 46 | 47 | run /bin/bash -c "sleep 10" 48 | echo "output: $output" 49 | echo "status: $status" 50 | [[ "$status" -eq 0 ]] 51 | 52 | run /bin/bash -c "aws lambda get-function --function-name '$LAMBDA_FUNCTION_NAME'" 53 | echo "output: $output" 54 | echo "status: $status" 55 | [[ "$status" -eq 0 ]] 56 | 57 | run /bin/bash -c "aws lambda invoke --cli-binary-format raw-in-base64-out --function-name '$LAMBDA_FUNCTION_NAME' --payload '{\"name\": \"World\"}' response.json" 58 | echo "output: $output" 59 | echo "status: $status" 60 | [[ "$status" -eq 0 ]] 61 | } 62 | -------------------------------------------------------------------------------- /tests/lambda.yml-invalid-image/Function.cs: -------------------------------------------------------------------------------- 1 | // Compile with: 2 | // docker run --rm -v "$PWD":/var/task mlupin/docker-lambda:build-dotnetcore3.1 dotnet publish -c Release -o pub 3 | 4 | // Run with: 5 | // docker run --rm -v "$PWD"/pub:/var/task mlupin/docker-lambda:dotnetcore3.1 test::test.Function::FunctionHandler '{"some": "event"}' 6 | 7 | using System; 8 | using System.Collections; 9 | using Amazon.Lambda.Core; 10 | 11 | [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] 12 | 13 | namespace test 14 | { 15 | public class Function 16 | { 17 | public string FunctionHandler(object inputEvent, ILambdaContext context) 18 | { 19 | Console.WriteLine($"inputEvent: {inputEvent}"); 20 | Console.WriteLine($"RemainingTime: {context.RemainingTime}"); 21 | 22 | foreach (DictionaryEntry kv in Environment.GetEnvironmentVariables()) 23 | { 24 | Console.WriteLine($"{kv.Key}={kv.Value}"); 25 | } 26 | 27 | return "Hello World!"; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/lambda.yml-invalid-image/lambda.yml: -------------------------------------------------------------------------------- 1 | --- 2 | build_image: mlupin/invalid:image 3 | builder: dotnet 4 | -------------------------------------------------------------------------------- /tests/lambda.yml-invalid-image/test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/lambda.yml-invalid-image/test.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test", "test.csproj", "{0A83D120-2336-4F30-86F1-DC045C3C9B90}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | EndGlobal 18 | -------------------------------------------------------------------------------- /tests/lambda.yml-nonexistent-builder/Function.cs: -------------------------------------------------------------------------------- 1 | // Compile with: 2 | // docker run --rm -v "$PWD":/var/task mlupin/docker-lambda:build-dotnetcore3.1 dotnet publish -c Release -o pub 3 | 4 | // Run with: 5 | // docker run --rm -v "$PWD"/pub:/var/task mlupin/docker-lambda:dotnetcore3.1 test::test.Function::FunctionHandler '{"some": "event"}' 6 | 7 | using System; 8 | using System.Collections; 9 | using Amazon.Lambda.Core; 10 | 11 | [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] 12 | 13 | namespace test 14 | { 15 | public class Function 16 | { 17 | public string FunctionHandler(object inputEvent, ILambdaContext context) 18 | { 19 | Console.WriteLine($"inputEvent: {inputEvent}"); 20 | Console.WriteLine($"RemainingTime: {context.RemainingTime}"); 21 | 22 | foreach (DictionaryEntry kv in Environment.GetEnvironmentVariables()) 23 | { 24 | Console.WriteLine($"{kv.Key}={kv.Value}"); 25 | } 26 | 27 | return "Hello World!"; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/lambda.yml-nonexistent-builder/lambda.yml: -------------------------------------------------------------------------------- 1 | --- 2 | build_image: mlupin/docker-lambda:dotnetcore3.1-build 3 | builder: nonexistent 4 | -------------------------------------------------------------------------------- /tests/lambda.yml-nonexistent-builder/test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/lambda.yml-nonexistent-builder/test.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test", "test.csproj", "{0A83D120-2336-4F30-86F1-DC045C3C9B90}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | EndGlobal 18 | -------------------------------------------------------------------------------- /tests/lambda.yml/Function.cs: -------------------------------------------------------------------------------- 1 | // Compile with: 2 | // docker run --rm -v "$PWD":/var/task mlupin/docker-lambda:build-dotnetcore3.1 dotnet publish -c Release -o pub 3 | 4 | // Run with: 5 | // docker run --rm -v "$PWD"/pub:/var/task mlupin/docker-lambda:dotnetcore3.1 test::test.Function::FunctionHandler '{"some": "event"}' 6 | 7 | using System; 8 | using System.Collections; 9 | using Amazon.Lambda.Core; 10 | 11 | [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] 12 | 13 | namespace test 14 | { 15 | public class Function 16 | { 17 | public string FunctionHandler(object inputEvent, ILambdaContext context) 18 | { 19 | Console.WriteLine($"inputEvent: {inputEvent}"); 20 | Console.WriteLine($"RemainingTime: {context.RemainingTime}"); 21 | 22 | foreach (DictionaryEntry kv in Environment.GetEnvironmentVariables()) 23 | { 24 | Console.WriteLine($"{kv.Key}={kv.Value}"); 25 | } 26 | 27 | return "Hello World!"; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/lambda.yml/lambda.yml: -------------------------------------------------------------------------------- 1 | --- 2 | build_image: mlupin/docker-lambda:dotnetcore3.1-build 3 | builder: dotnet 4 | -------------------------------------------------------------------------------- /tests/lambda.yml/test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | export LAMBDA_ROLE="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 4 | export AWS_ACCOUNT_ID="$(aws sts get-caller-identity | jq -r ".Account")" 5 | export LAMBDA_FUNCTION_NAME=lambda-dotnetcore31 6 | export LAMBDA_RUNTIME=dotnetcore3.1 7 | export LAMBDA_HANDLER=test::test.Function::FunctionHandler 8 | 9 | setup() { 10 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 11 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 12 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 13 | } 14 | 15 | teardown() { 16 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 17 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 18 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 19 | } 20 | 21 | @test "aws test" { 22 | run /bin/bash -c "lambda-builder build" 23 | echo "output: $output" 24 | echo "status: $status" 25 | [[ "$status" -eq 0 ]] 26 | 27 | run /bin/bash -c "aws iam create-role --role-name '$LAMBDA_FUNCTION_NAME' --tags 'Key=app,Value=lambda-builder' --tags 'Key=com.dokku.lambda-builder/runtime,Value=$LAMBDA_RUNTIME' --assume-role-policy-document '{\"Version\": \"2012-10-17\", \"Statement\": [{ \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"lambda.amazonaws.com\"}, \"Action\": \"sts:AssumeRole\"}]}'" 28 | echo "output: $output" 29 | echo "status: $status" 30 | [[ "$status" -eq 0 ]] 31 | 32 | run /bin/bash -c "aws iam attach-role-policy --role-name '$LAMBDA_FUNCTION_NAME' --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 33 | echo "output: $output" 34 | echo "status: $status" 35 | [[ "$status" -eq 0 ]] 36 | 37 | run /bin/bash -c "sleep 10" 38 | echo "output: $output" 39 | echo "status: $status" 40 | [[ "$status" -eq 0 ]] 41 | 42 | run /bin/bash -c "aws lambda create-function --function-name '$LAMBDA_FUNCTION_NAME' --package-type Zip --tags 'app=lambda-builder,com.dokku.lambda-builder/runtime=$LAMBDA_RUNTIME' --role 'arn:aws:iam::${AWS_ACCOUNT_ID}:role/$LAMBDA_FUNCTION_NAME' --zip-file fileb://lambda.zip --runtime '$LAMBDA_RUNTIME' --handler '$LAMBDA_HANDLER'" 43 | echo "output: $output" 44 | echo "status: $status" 45 | [[ "$status" -eq 0 ]] 46 | 47 | run /bin/bash -c "sleep 10" 48 | echo "output: $output" 49 | echo "status: $status" 50 | [[ "$status" -eq 0 ]] 51 | 52 | run /bin/bash -c "aws lambda get-function --function-name '$LAMBDA_FUNCTION_NAME'" 53 | echo "output: $output" 54 | echo "status: $status" 55 | [[ "$status" -eq 0 ]] 56 | 57 | run /bin/bash -c "aws lambda invoke --cli-binary-format raw-in-base64-out --function-name '$LAMBDA_FUNCTION_NAME' --payload '{\"name\": \"World\"}' response.json" 58 | echo "output: $output" 59 | echo "status: $status" 60 | [[ "$status" -eq 0 ]] 61 | } 62 | -------------------------------------------------------------------------------- /tests/lambda.yml/test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/lambda.yml/test.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test", "test.csproj", "{0A83D120-2336-4F30-86F1-DC045C3C9B90}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {0A83D120-2336-4F30-86F1-DC045C3C9B90}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | EndGlobal 18 | -------------------------------------------------------------------------------- /tests/not-detected/function.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def handler(event, context): 5 | response = requests.get("https://example.com") 6 | print(response.text) 7 | return "Hello World!" 8 | -------------------------------------------------------------------------------- /tests/npm/function.js: -------------------------------------------------------------------------------- 1 | exports.handler = async function(event, context) { 2 | console.log("EVENT: \n" + JSON.stringify(event, null, 2)) 3 | return "Hello World!" 4 | } 5 | -------------------------------------------------------------------------------- /tests/npm/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "app", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "left-pad": "^1.3.0" 13 | } 14 | }, 15 | "node_modules/left-pad": { 16 | "version": "1.3.0", 17 | "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", 18 | "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", 19 | "deprecated": "use String.prototype.padStart()" 20 | } 21 | }, 22 | "dependencies": { 23 | "left-pad": { 24 | "version": "1.3.0", 25 | "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", 26 | "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/npm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "left-pad": "^1.3.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/npm/test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | export LAMBDA_ROLE="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 4 | export AWS_ACCOUNT_ID="$(aws sts get-caller-identity | jq -r ".Account")" 5 | export LAMBDA_FUNCTION_NAME=lambda-nodejs14x 6 | export LAMBDA_RUNTIME=nodejs14.x 7 | export LAMBDA_HANDLER=function.handler 8 | 9 | setup() { 10 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 11 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 12 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 13 | } 14 | 15 | teardown() { 16 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 17 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 18 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 19 | } 20 | 21 | @test "aws test" { 22 | run /bin/bash -c "lambda-builder build" 23 | echo "output: $output" 24 | echo "status: $status" 25 | [[ "$status" -eq 0 ]] 26 | 27 | run /bin/bash -c "aws iam create-role --role-name '$LAMBDA_FUNCTION_NAME' --tags 'Key=app,Value=lambda-builder' --tags 'Key=com.dokku.lambda-builder/runtime,Value=$LAMBDA_RUNTIME' --assume-role-policy-document '{\"Version\": \"2012-10-17\", \"Statement\": [{ \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"lambda.amazonaws.com\"}, \"Action\": \"sts:AssumeRole\"}]}'" 28 | echo "output: $output" 29 | echo "status: $status" 30 | [[ "$status" -eq 0 ]] 31 | 32 | run /bin/bash -c "aws iam attach-role-policy --role-name '$LAMBDA_FUNCTION_NAME' --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 33 | echo "output: $output" 34 | echo "status: $status" 35 | [[ "$status" -eq 0 ]] 36 | 37 | run /bin/bash -c "sleep 10" 38 | echo "output: $output" 39 | echo "status: $status" 40 | [[ "$status" -eq 0 ]] 41 | 42 | run /bin/bash -c "aws lambda create-function --function-name '$LAMBDA_FUNCTION_NAME' --package-type Zip --tags 'app=lambda-builder,com.dokku.lambda-builder/runtime=$LAMBDA_RUNTIME' --role 'arn:aws:iam::${AWS_ACCOUNT_ID}:role/$LAMBDA_FUNCTION_NAME' --zip-file fileb://lambda.zip --runtime '$LAMBDA_RUNTIME' --handler '$LAMBDA_HANDLER'" 43 | echo "output: $output" 44 | echo "status: $status" 45 | [[ "$status" -eq 0 ]] 46 | 47 | run /bin/bash -c "sleep 10" 48 | echo "output: $output" 49 | echo "status: $status" 50 | [[ "$status" -eq 0 ]] 51 | 52 | run /bin/bash -c "aws lambda get-function --function-name '$LAMBDA_FUNCTION_NAME'" 53 | echo "output: $output" 54 | echo "status: $status" 55 | [[ "$status" -eq 0 ]] 56 | 57 | run /bin/bash -c "aws lambda invoke --cli-binary-format raw-in-base64-out --function-name '$LAMBDA_FUNCTION_NAME' --payload '{\"name\": \"World\"}' response.json" 58 | echo "output: $output" 59 | echo "status: $status" 60 | [[ "$status" -eq 0 ]] 61 | } 62 | -------------------------------------------------------------------------------- /tests/pip-runtime/function.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def handler(event, context): 5 | response = requests.get("https://example.com") 6 | print(response.text) 7 | return "Hello World!" 8 | -------------------------------------------------------------------------------- /tests/pip-runtime/requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.32.2 -------------------------------------------------------------------------------- /tests/pip-runtime/runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.8.9 2 | -------------------------------------------------------------------------------- /tests/pip-runtime/test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | export LAMBDA_ROLE="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 4 | export AWS_ACCOUNT_ID="$(aws sts get-caller-identity | jq -r ".Account")" 5 | export LAMBDA_FUNCTION_NAME=lambda-python39-pip-runtime 6 | export LAMBDA_RUNTIME=python3.8 7 | export LAMBDA_HANDLER=function.handler 8 | 9 | setup() { 10 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 11 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 12 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 13 | } 14 | 15 | teardown() { 16 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 17 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 18 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 19 | } 20 | 21 | @test "aws test" { 22 | run /bin/bash -c "lambda-builder build" 23 | echo "output: $output" 24 | echo "status: $status" 25 | [[ "$status" -eq 0 ]] 26 | 27 | run /bin/bash -c "aws iam create-role --role-name '$LAMBDA_FUNCTION_NAME' --tags 'Key=app,Value=lambda-builder' --tags 'Key=com.dokku.lambda-builder/runtime,Value=$LAMBDA_RUNTIME' --assume-role-policy-document '{\"Version\": \"2012-10-17\", \"Statement\": [{ \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"lambda.amazonaws.com\"}, \"Action\": \"sts:AssumeRole\"}]}'" 28 | echo "output: $output" 29 | echo "status: $status" 30 | [[ "$status" -eq 0 ]] 31 | 32 | run /bin/bash -c "aws iam attach-role-policy --role-name '$LAMBDA_FUNCTION_NAME' --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 33 | echo "output: $output" 34 | echo "status: $status" 35 | [[ "$status" -eq 0 ]] 36 | 37 | run /bin/bash -c "sleep 10" 38 | echo "output: $output" 39 | echo "status: $status" 40 | [[ "$status" -eq 0 ]] 41 | 42 | run /bin/bash -c "aws lambda create-function --function-name '$LAMBDA_FUNCTION_NAME' --package-type Zip --tags 'app=lambda-builder,com.dokku.lambda-builder/runtime=$LAMBDA_RUNTIME' --role 'arn:aws:iam::${AWS_ACCOUNT_ID}:role/$LAMBDA_FUNCTION_NAME' --zip-file fileb://lambda.zip --runtime '$LAMBDA_RUNTIME' --handler '$LAMBDA_HANDLER'" 43 | echo "output: $output" 44 | echo "status: $status" 45 | [[ "$status" -eq 0 ]] 46 | 47 | run /bin/bash -c "sleep 10" 48 | echo "output: $output" 49 | echo "status: $status" 50 | [[ "$status" -eq 0 ]] 51 | 52 | run /bin/bash -c "aws lambda get-function --function-name '$LAMBDA_FUNCTION_NAME'" 53 | echo "output: $output" 54 | echo "status: $status" 55 | [[ "$status" -eq 0 ]] 56 | 57 | run /bin/bash -c "aws lambda invoke --cli-binary-format raw-in-base64-out --function-name '$LAMBDA_FUNCTION_NAME' --payload '{\"name\": \"World\"}' response.json" 58 | echo "output: $output" 59 | echo "status: $status" 60 | [[ "$status" -eq 0 ]] 61 | } 62 | -------------------------------------------------------------------------------- /tests/pip/function.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def handler(event, context): 5 | response = requests.get("https://example.com") 6 | print(response.text) 7 | return "Hello World!" 8 | -------------------------------------------------------------------------------- /tests/pip/requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.32.2 -------------------------------------------------------------------------------- /tests/pip/test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | export LAMBDA_ROLE="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 4 | export AWS_ACCOUNT_ID="$(aws sts get-caller-identity | jq -r ".Account")" 5 | export LAMBDA_FUNCTION_NAME=lambda-python39-pip 6 | export LAMBDA_RUNTIME=python3.9 7 | export LAMBDA_HANDLER=function.handler 8 | 9 | setup() { 10 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 11 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 12 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 13 | } 14 | 15 | teardown() { 16 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 17 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 18 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 19 | } 20 | 21 | @test "aws test" { 22 | run /bin/bash -c "lambda-builder build" 23 | echo "output: $output" 24 | echo "status: $status" 25 | [[ "$status" -eq 0 ]] 26 | 27 | run /bin/bash -c "aws iam create-role --role-name '$LAMBDA_FUNCTION_NAME' --tags 'Key=app,Value=lambda-builder' --tags 'Key=com.dokku.lambda-builder/runtime,Value=$LAMBDA_RUNTIME' --assume-role-policy-document '{\"Version\": \"2012-10-17\", \"Statement\": [{ \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"lambda.amazonaws.com\"}, \"Action\": \"sts:AssumeRole\"}]}'" 28 | echo "output: $output" 29 | echo "status: $status" 30 | [[ "$status" -eq 0 ]] 31 | 32 | run /bin/bash -c "aws iam attach-role-policy --role-name '$LAMBDA_FUNCTION_NAME' --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 33 | echo "output: $output" 34 | echo "status: $status" 35 | [[ "$status" -eq 0 ]] 36 | 37 | run /bin/bash -c "sleep 10" 38 | echo "output: $output" 39 | echo "status: $status" 40 | [[ "$status" -eq 0 ]] 41 | 42 | run /bin/bash -c "aws lambda create-function --function-name '$LAMBDA_FUNCTION_NAME' --package-type Zip --tags 'app=lambda-builder,com.dokku.lambda-builder/runtime=$LAMBDA_RUNTIME' --role 'arn:aws:iam::${AWS_ACCOUNT_ID}:role/$LAMBDA_FUNCTION_NAME' --zip-file fileb://lambda.zip --runtime '$LAMBDA_RUNTIME' --handler '$LAMBDA_HANDLER'" 43 | echo "output: $output" 44 | echo "status: $status" 45 | [[ "$status" -eq 0 ]] 46 | 47 | run /bin/bash -c "sleep 10" 48 | echo "output: $output" 49 | echo "status: $status" 50 | [[ "$status" -eq 0 ]] 51 | 52 | run /bin/bash -c "aws lambda get-function --function-name '$LAMBDA_FUNCTION_NAME'" 53 | echo "output: $output" 54 | echo "status: $status" 55 | [[ "$status" -eq 0 ]] 56 | 57 | run /bin/bash -c "aws lambda invoke --cli-binary-format raw-in-base64-out --function-name '$LAMBDA_FUNCTION_NAME' --payload '{\"name\": \"World\"}' response.json" 58 | echo "output: $output" 59 | echo "status: $status" 60 | [[ "$status" -eq 0 ]] 61 | } 62 | -------------------------------------------------------------------------------- /tests/pipenv/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | requests = "*" 8 | 9 | [dev-packages] 10 | 11 | [requires] 12 | python_version = "3.8" -------------------------------------------------------------------------------- /tests/pipenv/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "acbc8c4e7f2f98f1059b2a93d581ef43f4aa0c9741e64e6253adff8e35fbd99e" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "certifi": { 20 | "hashes": [ 21 | "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", 22 | "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90" 23 | ], 24 | "index": "pypi", 25 | "markers": "python_version >= '3.6'", 26 | "version": "==2024.7.4" 27 | }, 28 | "charset-normalizer": { 29 | "hashes": [ 30 | "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", 31 | "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", 32 | "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", 33 | "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", 34 | "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", 35 | "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", 36 | "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", 37 | "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", 38 | "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", 39 | "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", 40 | "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", 41 | "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", 42 | "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", 43 | "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", 44 | "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", 45 | "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", 46 | "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", 47 | "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", 48 | "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", 49 | "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", 50 | "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", 51 | "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", 52 | "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", 53 | "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", 54 | "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", 55 | "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", 56 | "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", 57 | "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", 58 | "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", 59 | "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", 60 | "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", 61 | "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", 62 | "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", 63 | "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", 64 | "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", 65 | "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", 66 | "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", 67 | "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", 68 | "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", 69 | "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", 70 | "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", 71 | "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", 72 | "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", 73 | "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", 74 | "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", 75 | "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", 76 | "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", 77 | "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", 78 | "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", 79 | "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", 80 | "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", 81 | "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", 82 | "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", 83 | "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", 84 | "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", 85 | "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", 86 | "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", 87 | "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", 88 | "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", 89 | "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", 90 | "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", 91 | "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", 92 | "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", 93 | "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", 94 | "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", 95 | "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", 96 | "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", 97 | "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", 98 | "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", 99 | "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", 100 | "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", 101 | "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", 102 | "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", 103 | "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", 104 | "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", 105 | "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", 106 | "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", 107 | "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", 108 | "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", 109 | "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", 110 | "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", 111 | "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", 112 | "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", 113 | "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", 114 | "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", 115 | "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", 116 | "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", 117 | "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", 118 | "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", 119 | "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" 120 | ], 121 | "markers": "python_full_version >= '3.7.0'", 122 | "version": "==3.3.2" 123 | }, 124 | "idna": { 125 | "hashes": [ 126 | "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", 127 | "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" 128 | ], 129 | "markers": "python_version >= '3.5'", 130 | "version": "==3.7" 131 | }, 132 | "requests": { 133 | "hashes": [ 134 | "sha256:f2c3881dddb70d056c5bd7600a4fae312b2a300e39be6a118d30b90bd27262b5", 135 | "sha256:fa5490319474c82ef1d2c9bc459d3652e3ae4ef4c4ebdd18a21145a47ca4b6b8" 136 | ], 137 | "index": "pypi", 138 | "markers": "python_version >= '3.8'", 139 | "version": "==2.32.0" 140 | }, 141 | "urllib3": { 142 | "hashes": [ 143 | "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", 144 | "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168" 145 | ], 146 | "index": "pypi", 147 | "markers": "python_version >= '3.8'", 148 | "version": "==2.2.2" 149 | } 150 | }, 151 | "develop": {} 152 | } 153 | -------------------------------------------------------------------------------- /tests/pipenv/function.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def handler(event, context): 5 | response = requests.get("https://example.com") 6 | print(response.text) 7 | return "Hello World!" 8 | -------------------------------------------------------------------------------- /tests/pipenv/test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | export LAMBDA_ROLE="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 4 | export AWS_ACCOUNT_ID="$(aws sts get-caller-identity | jq -r ".Account")" 5 | export LAMBDA_FUNCTION_NAME=lambda-python39-pipenv 6 | export LAMBDA_RUNTIME=python3.9 7 | export LAMBDA_HANDLER=function.handler 8 | 9 | setup() { 10 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 11 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 12 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 13 | } 14 | 15 | teardown() { 16 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 17 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 18 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 19 | } 20 | 21 | @test "aws test" { 22 | run /bin/bash -c "lambda-builder build" 23 | echo "output: $output" 24 | echo "status: $status" 25 | [[ "$status" -eq 0 ]] 26 | 27 | run /bin/bash -c "aws iam create-role --role-name '$LAMBDA_FUNCTION_NAME' --tags 'Key=app,Value=lambda-builder' --tags 'Key=com.dokku.lambda-builder/runtime,Value=$LAMBDA_RUNTIME' --assume-role-policy-document '{\"Version\": \"2012-10-17\", \"Statement\": [{ \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"lambda.amazonaws.com\"}, \"Action\": \"sts:AssumeRole\"}]}'" 28 | echo "output: $output" 29 | echo "status: $status" 30 | [[ "$status" -eq 0 ]] 31 | 32 | run /bin/bash -c "aws iam attach-role-policy --role-name '$LAMBDA_FUNCTION_NAME' --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 33 | echo "output: $output" 34 | echo "status: $status" 35 | [[ "$status" -eq 0 ]] 36 | 37 | run /bin/bash -c "sleep 10" 38 | echo "output: $output" 39 | echo "status: $status" 40 | [[ "$status" -eq 0 ]] 41 | 42 | run /bin/bash -c "aws lambda create-function --function-name '$LAMBDA_FUNCTION_NAME' --package-type Zip --tags 'app=lambda-builder,com.dokku.lambda-builder/runtime=$LAMBDA_RUNTIME' --role 'arn:aws:iam::${AWS_ACCOUNT_ID}:role/$LAMBDA_FUNCTION_NAME' --zip-file fileb://lambda.zip --runtime '$LAMBDA_RUNTIME' --handler '$LAMBDA_HANDLER'" 43 | echo "output: $output" 44 | echo "status: $status" 45 | [[ "$status" -eq 0 ]] 46 | 47 | run /bin/bash -c "sleep 10" 48 | echo "output: $output" 49 | echo "status: $status" 50 | [[ "$status" -eq 0 ]] 51 | 52 | run /bin/bash -c "aws lambda get-function --function-name '$LAMBDA_FUNCTION_NAME'" 53 | echo "output: $output" 54 | echo "status: $status" 55 | [[ "$status" -eq 0 ]] 56 | 57 | run /bin/bash -c "aws lambda invoke --cli-binary-format raw-in-base64-out --function-name '$LAMBDA_FUNCTION_NAME' --payload '{\"name\": \"World\"}' response.json" 58 | echo "output: $output" 59 | echo "status: $status" 60 | [[ "$status" -eq 0 ]] 61 | } 62 | -------------------------------------------------------------------------------- /tests/poetry/function.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def handler(event, context): 5 | response = requests.get("https://example.com") 6 | print(response.text) 7 | return "Hello World!" 8 | -------------------------------------------------------------------------------- /tests/poetry/poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "certifi" 5 | version = "2024.7.4" 6 | description = "Python package for providing Mozilla's CA Bundle." 7 | optional = false 8 | python-versions = ">=3.6" 9 | files = [ 10 | {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, 11 | {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, 12 | ] 13 | 14 | [[package]] 15 | name = "charset-normalizer" 16 | version = "2.0.12" 17 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 18 | optional = false 19 | python-versions = ">=3.5.0" 20 | files = [ 21 | {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, 22 | {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, 23 | ] 24 | 25 | [package.extras] 26 | unicode-backport = ["unicodedata2"] 27 | 28 | [[package]] 29 | name = "idna" 30 | version = "3.7" 31 | description = "Internationalized Domain Names in Applications (IDNA)" 32 | optional = false 33 | python-versions = ">=3.5" 34 | files = [ 35 | {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, 36 | {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, 37 | ] 38 | 39 | [[package]] 40 | name = "requests" 41 | version = "2.32.2" 42 | description = "Python HTTP for Humans." 43 | optional = false 44 | python-versions = ">=3.8" 45 | files = [ 46 | {file = "requests-2.32.2-py3-none-any.whl", hash = "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"}, 47 | {file = "requests-2.32.2.tar.gz", hash = "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289"}, 48 | ] 49 | 50 | [package.dependencies] 51 | certifi = ">=2017.4.17" 52 | charset-normalizer = ">=2,<4" 53 | idna = ">=2.5,<4" 54 | urllib3 = ">=1.21.1,<3" 55 | 56 | [package.extras] 57 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 58 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 59 | 60 | [[package]] 61 | name = "urllib3" 62 | version = "1.26.19" 63 | description = "HTTP library with thread-safe connection pooling, file post, and more." 64 | optional = false 65 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" 66 | files = [ 67 | {file = "urllib3-1.26.19-py2.py3-none-any.whl", hash = "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3"}, 68 | {file = "urllib3-1.26.19.tar.gz", hash = "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429"}, 69 | ] 70 | 71 | [package.extras] 72 | brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] 73 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] 74 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 75 | 76 | [metadata] 77 | lock-version = "2.0" 78 | python-versions = ">= 3.9" 79 | content-hash = "6b81fab1826f699cf49a6a2f04c9a781fb46e47d8950caee68f6a3eb2af5c470" 80 | -------------------------------------------------------------------------------- /tests/poetry/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "lambda" 3 | version = "0.1.0" 4 | description = "lambda project" 5 | authors = ["Your Name "] 6 | 7 | [tool.poetry.dependencies] 8 | python = ">= 3.9" 9 | requests = "^2.32.2" 10 | 11 | [tool.poetry.dev-dependencies] 12 | 13 | [build-system] 14 | requires = ["poetry-core>=1.0.0"] 15 | build-backend = "poetry.core.masonry.api" 16 | -------------------------------------------------------------------------------- /tests/poetry/test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | export LAMBDA_ROLE="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 4 | export AWS_ACCOUNT_ID="$(aws sts get-caller-identity | jq -r ".Account")" 5 | export LAMBDA_FUNCTION_NAME=lambda-python39-poetry 6 | export LAMBDA_RUNTIME=python3.9 7 | export LAMBDA_HANDLER=function.handler 8 | 9 | setup() { 10 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 11 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 12 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 13 | } 14 | 15 | teardown() { 16 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 17 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 18 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 19 | } 20 | 21 | @test "aws test" { 22 | run /bin/bash -c "lambda-builder build" 23 | echo "output: $output" 24 | echo "status: $status" 25 | [[ "$status" -eq 0 ]] 26 | 27 | run /bin/bash -c "aws iam create-role --role-name '$LAMBDA_FUNCTION_NAME' --tags 'Key=app,Value=lambda-builder' --tags 'Key=com.dokku.lambda-builder/runtime,Value=$LAMBDA_RUNTIME' --assume-role-policy-document '{\"Version\": \"2012-10-17\", \"Statement\": [{ \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"lambda.amazonaws.com\"}, \"Action\": \"sts:AssumeRole\"}]}'" 28 | echo "output: $output" 29 | echo "status: $status" 30 | [[ "$status" -eq 0 ]] 31 | 32 | run /bin/bash -c "aws iam attach-role-policy --role-name '$LAMBDA_FUNCTION_NAME' --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 33 | echo "output: $output" 34 | echo "status: $status" 35 | [[ "$status" -eq 0 ]] 36 | 37 | run /bin/bash -c "sleep 10" 38 | echo "output: $output" 39 | echo "status: $status" 40 | [[ "$status" -eq 0 ]] 41 | 42 | run /bin/bash -c "aws lambda create-function --function-name '$LAMBDA_FUNCTION_NAME' --package-type Zip --tags 'app=lambda-builder,com.dokku.lambda-builder/runtime=$LAMBDA_RUNTIME' --role 'arn:aws:iam::${AWS_ACCOUNT_ID}:role/$LAMBDA_FUNCTION_NAME' --zip-file fileb://lambda.zip --runtime '$LAMBDA_RUNTIME' --handler '$LAMBDA_HANDLER'" 43 | echo "output: $output" 44 | echo "status: $status" 45 | [[ "$status" -eq 0 ]] 46 | 47 | run /bin/bash -c "sleep 10" 48 | echo "output: $output" 49 | echo "status: $status" 50 | [[ "$status" -eq 0 ]] 51 | 52 | run /bin/bash -c "aws lambda get-function --function-name '$LAMBDA_FUNCTION_NAME'" 53 | echo "output: $output" 54 | echo "status: $status" 55 | [[ "$status" -eq 0 ]] 56 | 57 | run /bin/bash -c "aws lambda invoke --cli-binary-format raw-in-base64-out --function-name '$LAMBDA_FUNCTION_NAME' --payload '{\"name\": \"World\"}' response.json" 58 | echo "output: $output" 59 | echo "status: $status" 60 | [[ "$status" -eq 0 ]] 61 | } 62 | -------------------------------------------------------------------------------- /tests/ruby/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "example" 6 | -------------------------------------------------------------------------------- /tests/ruby/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | example (1.0.2) 5 | 6 | PLATFORMS 7 | ruby 8 | 9 | DEPENDENCIES 10 | example 11 | 12 | BUNDLED WITH 13 | 2.1.4 14 | -------------------------------------------------------------------------------- /tests/ruby/function.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | def handler(event:, context:) 4 | logger = Logger.new($stdout) 5 | 6 | logger.info(event) 7 | logger.info(context) 8 | "Hello World!" 9 | end -------------------------------------------------------------------------------- /tests/ruby/test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | export LAMBDA_ROLE="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 4 | export AWS_ACCOUNT_ID="$(aws sts get-caller-identity | jq -r ".Account")" 5 | export LAMBDA_FUNCTION_NAME=lambda-ruby27 6 | export LAMBDA_RUNTIME=ruby2.7 7 | export LAMBDA_HANDLER=function.handler 8 | 9 | setup() { 10 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 11 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 12 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 13 | } 14 | 15 | teardown() { 16 | aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 17 | aws iam detach-role-policy --role-name "$LAMBDA_FUNCTION_NAME" --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 2>/dev/null || true 18 | aws iam delete-role --role-name "$LAMBDA_FUNCTION_NAME" 2>/dev/null || true 19 | } 20 | 21 | @test "aws test" { 22 | run /bin/bash -c "lambda-builder build" 23 | echo "output: $output" 24 | echo "status: $status" 25 | [[ "$status" -eq 0 ]] 26 | 27 | run /bin/bash -c "aws iam create-role --role-name '$LAMBDA_FUNCTION_NAME' --tags 'Key=app,Value=lambda-builder' --tags 'Key=com.dokku.lambda-builder/runtime,Value=$LAMBDA_RUNTIME' --assume-role-policy-document '{\"Version\": \"2012-10-17\", \"Statement\": [{ \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"lambda.amazonaws.com\"}, \"Action\": \"sts:AssumeRole\"}]}'" 28 | echo "output: $output" 29 | echo "status: $status" 30 | [[ "$status" -eq 0 ]] 31 | 32 | run /bin/bash -c "aws iam attach-role-policy --role-name '$LAMBDA_FUNCTION_NAME' --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 33 | echo "output: $output" 34 | echo "status: $status" 35 | [[ "$status" -eq 0 ]] 36 | 37 | run /bin/bash -c "sleep 10" 38 | echo "output: $output" 39 | echo "status: $status" 40 | [[ "$status" -eq 0 ]] 41 | 42 | run /bin/bash -c "aws lambda create-function --function-name '$LAMBDA_FUNCTION_NAME' --package-type Zip --tags 'app=lambda-builder,com.dokku.lambda-builder/runtime=$LAMBDA_RUNTIME' --role 'arn:aws:iam::${AWS_ACCOUNT_ID}:role/$LAMBDA_FUNCTION_NAME' --zip-file fileb://lambda.zip --runtime '$LAMBDA_RUNTIME' --handler '$LAMBDA_HANDLER'" 43 | echo "output: $output" 44 | echo "status: $status" 45 | [[ "$status" -eq 0 ]] 46 | 47 | run /bin/bash -c "sleep 10" 48 | echo "output: $output" 49 | echo "status: $status" 50 | [[ "$status" -eq 0 ]] 51 | 52 | run /bin/bash -c "aws lambda get-function --function-name '$LAMBDA_FUNCTION_NAME'" 53 | echo "output: $output" 54 | echo "status: $status" 55 | [[ "$status" -eq 0 ]] 56 | 57 | run /bin/bash -c "aws lambda invoke --cli-binary-format raw-in-base64-out --function-name '$LAMBDA_FUNCTION_NAME' --payload '{\"name\": \"World\"}' response.json" 58 | echo "output: $output" 59 | echo "status: $status" 60 | [[ "$status" -eq 0 ]] 61 | } 62 | -------------------------------------------------------------------------------- /ui/human_writer.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "os" 9 | "path/filepath" 10 | "sort" 11 | "strconv" 12 | "sync" 13 | "time" 14 | 15 | "github.com/rs/zerolog" 16 | ) 17 | 18 | const ( 19 | colorBlack = iota + 30 20 | colorRed 21 | colorGreen 22 | colorYellow 23 | colorBlue 24 | colorMagenta 25 | colorCyan 26 | colorWhite 27 | 28 | colorBold = 1 29 | colorDarkGray = 90 30 | ) 31 | 32 | var ( 33 | consoleBufPool = sync.Pool{ 34 | New: func() interface{} { 35 | return bytes.NewBuffer(make([]byte, 0, 100)) 36 | }, 37 | } 38 | ) 39 | 40 | const ( 41 | consoleDefaultTimeFormat = time.Kitchen 42 | ) 43 | 44 | // HumanWriter parses the JSON input and writes it in an 45 | // (optionally) colorized, human-friendly format to Out. 46 | type HumanWriter struct { 47 | // Out is the output destination. 48 | Out io.Writer 49 | 50 | // NoColor disables the colorized output. 51 | NoColor bool 52 | 53 | // TimeFormat specifies the format for timestamp in output. 54 | TimeFormat string 55 | 56 | // PartsOrder defines the order of parts in output. 57 | PartsOrder []string 58 | 59 | // PartsExclude defines parts to not display in output. 60 | PartsExclude []string 61 | 62 | FormatTimestamp zerolog.Formatter 63 | FormatLevel zerolog.Formatter 64 | FormatCaller zerolog.Formatter 65 | FormatMessage zerolog.Formatter 66 | FormatFieldName zerolog.Formatter 67 | FormatFieldValue zerolog.Formatter 68 | FormatErrFieldName zerolog.Formatter 69 | FormatErrFieldValue zerolog.Formatter 70 | } 71 | 72 | // NewConsoleWriter creates and initializes a new ConsoleWriter. 73 | func NewConsoleWriter(options ...func(w *HumanWriter)) HumanWriter { 74 | w := HumanWriter{ 75 | Out: os.Stdout, 76 | TimeFormat: consoleDefaultTimeFormat, 77 | PartsOrder: consoleDefaultPartsOrder(), 78 | } 79 | 80 | for _, opt := range options { 81 | opt(&w) 82 | } 83 | 84 | return w 85 | } 86 | 87 | // Write transforms the JSON input with formatters and appends to w.Out. 88 | func (w HumanWriter) Write(p []byte) (n int, err error) { 89 | if w.PartsOrder == nil { 90 | w.PartsOrder = consoleDefaultPartsOrder() 91 | } 92 | 93 | var buf = consoleBufPool.Get().(*bytes.Buffer) 94 | defer func() { 95 | buf.Reset() 96 | consoleBufPool.Put(buf) 97 | }() 98 | 99 | var evt map[string]interface{} 100 | // p = decodeIfBinaryToBytes(p) 101 | d := json.NewDecoder(bytes.NewReader(p)) 102 | d.UseNumber() 103 | err = d.Decode(&evt) 104 | if err != nil { 105 | return n, fmt.Errorf("cannot decode event: %s", err) 106 | } 107 | 108 | val, ok := evt[zerolog.LevelFieldName].(string) 109 | if !ok { 110 | buf.WriteString(" ") 111 | } else { 112 | switch val { 113 | case "trace": 114 | buf.WriteString(colorize(colorize(" ++ ", colorMagenta, w.NoColor), colorBold, w.NoColor)) 115 | case "debug": 116 | buf.WriteString(colorize(colorize(" + ", colorMagenta, w.NoColor), colorBold, w.NoColor)) 117 | case "info": 118 | if headerLevel, ok := evt["header"].(json.Number); ok { 119 | delete(evt, "header") 120 | if headerLevel == "1" { 121 | buf.WriteString("=====> ") 122 | } else if headerLevel == "2" { 123 | buf.WriteString("-----> ") 124 | } else { 125 | buf.WriteString(" > ") 126 | } 127 | } else { 128 | buf.WriteString(" ") 129 | } 130 | case "warn": 131 | buf.WriteString(colorize(" ? ", colorYellow, w.NoColor)) 132 | case "error": 133 | buf.WriteString(colorize(colorize(" ! ", colorRed, w.NoColor), colorBold, w.NoColor)) 134 | case "fatal": 135 | buf.WriteString(colorize(colorize(" ! ", colorRed, w.NoColor), colorBold, w.NoColor)) 136 | case "panic": 137 | buf.WriteString(colorize(colorize(" ! ", colorRed, w.NoColor), colorBold, w.NoColor)) 138 | } 139 | } 140 | 141 | for _, p := range w.PartsOrder { 142 | w.writePart(buf, evt, p) 143 | } 144 | 145 | w.writeFields(evt, buf) 146 | 147 | err = buf.WriteByte('\n') 148 | if err != nil { 149 | return n, err 150 | } 151 | _, err = buf.WriteTo(w.Out) 152 | return len(p), err 153 | } 154 | 155 | // writeFields appends formatted key-value pairs to buf. 156 | func (w HumanWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer) { 157 | var fields = make([]string, 0, len(evt)) 158 | for field := range evt { 159 | switch field { 160 | case zerolog.LevelFieldName, zerolog.TimestampFieldName, zerolog.MessageFieldName, zerolog.CallerFieldName: 161 | continue 162 | } 163 | fields = append(fields, field) 164 | } 165 | sort.Strings(fields) 166 | 167 | if len(fields) > 0 { 168 | buf.WriteByte(' ') 169 | } 170 | 171 | // Move the "error" field to the front 172 | ei := sort.Search(len(fields), func(i int) bool { return fields[i] >= zerolog.ErrorFieldName }) 173 | if ei < len(fields) && fields[ei] == zerolog.ErrorFieldName { 174 | fields[ei] = "" 175 | fields = append([]string{zerolog.ErrorFieldName}, fields...) 176 | var xfields = make([]string, 0, len(fields)) 177 | for _, field := range fields { 178 | if field == "" { // Skip empty fields 179 | continue 180 | } 181 | xfields = append(xfields, field) 182 | } 183 | fields = xfields 184 | } 185 | 186 | for i, field := range fields { 187 | var fn zerolog.Formatter 188 | var fv zerolog.Formatter 189 | 190 | if field == zerolog.ErrorFieldName { 191 | if w.FormatErrFieldName == nil { 192 | fn = consoleDefaultFormatErrFieldName(w.NoColor) 193 | } else { 194 | fn = w.FormatErrFieldName 195 | } 196 | 197 | if w.FormatErrFieldValue == nil { 198 | fv = consoleDefaultFormatErrFieldValue(w.NoColor) 199 | } else { 200 | fv = w.FormatErrFieldValue 201 | } 202 | } else { 203 | if w.FormatFieldName == nil { 204 | fn = consoleDefaultFormatFieldName(w.NoColor) 205 | } else { 206 | fn = w.FormatFieldName 207 | } 208 | 209 | if w.FormatFieldValue == nil { 210 | fv = consoleDefaultFormatFieldValue 211 | } else { 212 | fv = w.FormatFieldValue 213 | } 214 | } 215 | 216 | buf.WriteString(fn(field)) 217 | 218 | switch fValue := evt[field].(type) { 219 | case string: 220 | if needsQuote(fValue) { 221 | buf.WriteString(fv(strconv.Quote(fValue))) 222 | } else { 223 | buf.WriteString(fv(fValue)) 224 | } 225 | case json.Number: 226 | buf.WriteString(fv(fValue)) 227 | default: 228 | b, err := json.Marshal(fValue) 229 | if err != nil { 230 | fmt.Fprintf(buf, colorize("[error: %v]", colorRed, w.NoColor), err) 231 | } else { 232 | fmt.Fprint(buf, fv(b)) 233 | } 234 | } 235 | 236 | if i < len(fields)-1 { // Skip space for last field 237 | buf.WriteByte(' ') 238 | } 239 | } 240 | } 241 | 242 | // writePart appends a formatted part to buf. 243 | func (w HumanWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, p string) { 244 | var f zerolog.Formatter 245 | 246 | if w.PartsExclude != nil && len(w.PartsExclude) > 0 { 247 | for _, exclude := range w.PartsExclude { 248 | if exclude == p { 249 | return 250 | } 251 | } 252 | } 253 | 254 | switch p { 255 | case zerolog.LevelFieldName: 256 | if w.FormatLevel == nil { 257 | f = consoleDefaultFormatLevel(w.NoColor) 258 | } else { 259 | f = w.FormatLevel 260 | } 261 | case zerolog.TimestampFieldName: 262 | if w.FormatTimestamp == nil { 263 | f = consoleDefaultFormatTimestamp(w.TimeFormat, w.NoColor) 264 | } else { 265 | f = w.FormatTimestamp 266 | } 267 | case zerolog.MessageFieldName: 268 | if w.FormatMessage == nil { 269 | f = consoleDefaultFormatMessage 270 | } else { 271 | f = w.FormatMessage 272 | } 273 | case zerolog.CallerFieldName: 274 | if w.FormatCaller == nil { 275 | f = consoleDefaultFormatCaller(w.NoColor) 276 | } else { 277 | f = w.FormatCaller 278 | } 279 | default: 280 | if w.FormatFieldValue == nil { 281 | f = consoleDefaultFormatFieldValue 282 | } else { 283 | f = w.FormatFieldValue 284 | } 285 | } 286 | 287 | var s = f(evt[p]) 288 | 289 | if len(s) > 0 { 290 | buf.WriteString(s) 291 | if p != w.PartsOrder[len(w.PartsOrder)-1] { // Skip space for last part 292 | buf.WriteByte(' ') 293 | } 294 | } 295 | } 296 | 297 | // needsQuote returns true when the string s should be quoted in output. 298 | func needsQuote(s string) bool { 299 | for i := range s { 300 | if s[i] < 0x20 || s[i] > 0x7e || s[i] == ' ' || s[i] == '\\' || s[i] == '"' { 301 | return true 302 | } 303 | } 304 | return false 305 | } 306 | 307 | // colorize returns the string s wrapped in ANSI code c, unless disabled is true. 308 | func colorize(s interface{}, c int, disabled bool) string { 309 | if disabled { 310 | return fmt.Sprintf("%s", s) 311 | } 312 | return fmt.Sprintf("\x1b[%dm%v\x1b[0m", c, s) 313 | } 314 | 315 | // ----- DEFAULT FORMATTERS --------------------------------------------------- 316 | 317 | func consoleDefaultPartsOrder() []string { 318 | return []string{ 319 | zerolog.TimestampFieldName, 320 | zerolog.LevelFieldName, 321 | zerolog.CallerFieldName, 322 | zerolog.MessageFieldName, 323 | } 324 | } 325 | 326 | func consoleDefaultFormatTimestamp(timeFormat string, noColor bool) zerolog.Formatter { 327 | return func(i interface{}) string { 328 | return "" 329 | } 330 | } 331 | 332 | func consoleDefaultFormatLevel(noColor bool) zerolog.Formatter { 333 | return func(i interface{}) string { 334 | return "" 335 | } 336 | } 337 | 338 | func consoleDefaultFormatCaller(noColor bool) zerolog.Formatter { 339 | return func(i interface{}) string { 340 | var c string 341 | if cc, ok := i.(string); ok { 342 | c = cc 343 | } 344 | if len(c) > 0 { 345 | if cwd, err := os.Getwd(); err == nil { 346 | if rel, err := filepath.Rel(cwd, c); err == nil { 347 | c = rel 348 | } 349 | } 350 | c = colorize(c, colorBold, noColor) + colorize(" >", colorCyan, noColor) 351 | } 352 | return c 353 | } 354 | } 355 | 356 | func consoleDefaultFormatMessage(i interface{}) string { 357 | if i == nil { 358 | return "" 359 | } 360 | 361 | return fmt.Sprintf("%-80s", i) 362 | } 363 | 364 | func consoleDefaultFormatFieldName(noColor bool) zerolog.Formatter { 365 | return func(i interface{}) string { 366 | return colorize(fmt.Sprintf("%s=", i), colorCyan, noColor) 367 | } 368 | } 369 | 370 | func consoleDefaultFormatFieldValue(i interface{}) string { 371 | return fmt.Sprintf("%s", i) 372 | } 373 | 374 | func consoleDefaultFormatErrFieldName(noColor bool) zerolog.Formatter { 375 | return func(i interface{}) string { 376 | return colorize(fmt.Sprintf("%s=", i), colorRed, noColor) 377 | } 378 | } 379 | 380 | func consoleDefaultFormatErrFieldValue(noColor bool) zerolog.Formatter { 381 | return func(i interface{}) string { 382 | return colorize(fmt.Sprintf("%s", i), colorRed, noColor) 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /ui/zerolog_ui.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/mattn/go-isatty" 7 | "github.com/mitchellh/cli" 8 | "github.com/rs/zerolog" 9 | ) 10 | 11 | var isTerminal bool = false 12 | 13 | type ZerologUi struct { 14 | StderrLogger zerolog.Logger 15 | StdoutLogger zerolog.Logger 16 | OriginalFields map[string]interface{} 17 | Ui cli.Ui 18 | } 19 | 20 | func (u *ZerologUi) Ask(query string) (string, error) { 21 | return u.Ui.Ask(query) 22 | } 23 | 24 | func (u *ZerologUi) AskSecret(query string) (string, error) { 25 | return u.Ui.AskSecret(query) 26 | } 27 | 28 | func (u *ZerologUi) Error(message string) { 29 | u.StderrLogger.Error().Msg(message) 30 | } 31 | 32 | func (u *ZerologUi) Info(message string) { 33 | u.StdoutLogger.Info().Msg(message) 34 | } 35 | 36 | func (u *ZerologUi) Output(message string) { 37 | u.StdoutLogger.Info().Msg(message) 38 | } 39 | 40 | func (u *ZerologUi) Warn(message string) { 41 | u.StderrLogger.Warn().Msg(message) 42 | } 43 | 44 | func (u *ZerologUi) LogHeader1(message string) { 45 | u.StdoutLogger.Info().Int("header", 1).Msg(message) 46 | } 47 | 48 | func (u *ZerologUi) LogHeader2(message string) { 49 | u.StdoutLogger.Info().Int("header", 2).Msg(message) 50 | } 51 | 52 | func (u *ZerologUi) Field(field string, value interface{}) *ZerologUi { 53 | fields := make(map[string]interface{}, len(u.OriginalFields)+1) 54 | for k, v := range u.OriginalFields { 55 | fields[k] = v 56 | } 57 | 58 | fields[field] = value 59 | return ZerologUiWithFields(u.Ui, fields) 60 | } 61 | 62 | func (u *ZerologUi) Fields(newFields map[string]interface{}) *ZerologUi { 63 | fields := make(map[string]interface{}, len(u.OriginalFields)+len(newFields)) 64 | for k, v := range u.OriginalFields { 65 | fields[k] = v 66 | } 67 | for k, v := range newFields { 68 | fields[k] = v 69 | } 70 | 71 | return ZerologUiWithFields(u.Ui, fields) 72 | } 73 | 74 | func ZerologUiWithFields(ui cli.Ui, fields map[string]interface{}) *ZerologUi { 75 | return &ZerologUi{ 76 | StderrLogger: zerolog.New(HumanWriter{Out: os.Stderr}).With().Fields(fields).Timestamp().Logger(), 77 | StdoutLogger: zerolog.New(HumanWriter{Out: os.Stdout}).With().Fields(fields).Timestamp().Logger(), 78 | OriginalFields: fields, 79 | Ui: ui, 80 | } 81 | } 82 | 83 | func init() { 84 | isTerminal = isatty.IsTerminal(os.Stdout.Fd()) 85 | } 86 | --------------------------------------------------------------------------------