├── .github ├── dependabot.yml └── workflows │ ├── analyze.yaml │ ├── boilerplate.yaml │ ├── build.yaml │ ├── donotsubmit.yaml │ ├── e2e.yaml │ ├── image.yaml │ ├── kind-e2e.yaml │ ├── modules-integration-test.yaml │ ├── publish-site.yaml │ ├── registries.yaml │ ├── release.yml │ ├── sbom.yaml │ ├── stale.yaml │ ├── style.yaml │ ├── test.yaml │ └── verify.yaml ├── .gitignore ├── .golangci.yaml ├── .goreleaser.yml ├── .ko.yaml ├── .wokeignore ├── CNAME ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── ROADMAP.md ├── cmd └── help │ └── main.go ├── code-of-conduct.md ├── docs ├── CNAME ├── README.md ├── advanced │ ├── faq.md │ ├── go-packages.md │ ├── lambda.md │ ├── limitations.md │ ├── linux-capabilities.md │ ├── migrating-from-dockerfile.md │ ├── root-ca-certificates.md │ └── terraform.md ├── community.md ├── configuration.md ├── custom │ ├── main.html │ └── partials │ │ └── copyright.html ├── deployment.md ├── features │ ├── build-cache.md │ ├── debugging.md │ ├── k8s.md │ ├── multi-platform.md │ ├── sboms.md │ └── static-assets.md ├── get-started.md ├── images │ ├── android-icon-192x192.png │ ├── apple-icon-180x180.png │ ├── apple-icon.png │ ├── cncf-dark.svg │ ├── cncf-light.svg │ ├── demo.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── ko-with-name.svg │ ├── ko.png │ ├── ko.svg │ ├── ms-icon-144x144.png │ └── og.png ├── index.md ├── install.md └── reference │ ├── ko.md │ ├── ko_apply.md │ ├── ko_build.md │ ├── ko_create.md │ ├── ko_delete.md │ ├── ko_login.md │ ├── ko_resolve.md │ ├── ko_run.md │ └── ko_version.md ├── go.mod ├── go.sum ├── hack ├── boilerplate │ ├── boilerplate.go.txt │ └── boilerplate.sh.txt ├── presubmit.sh ├── tools.go ├── update-codegen.sh └── update-deps.sh ├── integration_test.sh ├── internal └── sbom │ ├── sbom.go │ └── spdx.go ├── main.go ├── mkdocs.yml ├── pkg ├── build │ ├── build.go │ ├── cache.go │ ├── config.go │ ├── doc.go │ ├── future.go │ ├── future_test.go │ ├── gobuild.go │ ├── gobuild_test.go │ ├── gobuilds.go │ ├── gobuilds_test.go │ ├── layer.go │ ├── limit.go │ ├── limit_test.go │ ├── options.go │ ├── recorder.go │ ├── recorder_test.go │ ├── shared.go │ ├── shared_test.go │ ├── strict.go │ └── strict_test.go ├── caps │ ├── caps.go │ ├── caps_dd_test.go │ ├── caps_test.go │ ├── gen.sh │ └── new_file_caps_test.go ├── commands │ ├── apply.go │ ├── build.go │ ├── cache.go │ ├── commands.go │ ├── config.go │ ├── config_test.go │ ├── create.go │ ├── delete.go │ ├── options │ │ ├── build.go │ │ ├── build_test.go │ │ ├── filestuff.go │ │ ├── namer_test.go │ │ ├── publish.go │ │ ├── selector.go │ │ ├── testdata │ │ │ ├── bad-config │ │ │ │ └── .ko.yaml │ │ │ │ │ └── .gitignore │ │ │ ├── config │ │ │ │ ├── .ko.yaml │ │ │ │ └── my-ko.yaml │ │ │ ├── multiple-platforms │ │ │ │ └── .ko.yaml │ │ │ └── paths │ │ │ │ ├── .ko.yaml │ │ │ │ └── app │ │ │ │ ├── cmd │ │ │ │ └── foo │ │ │ │ │ └── main.go │ │ │ │ └── go.mod │ │ └── validate.go │ ├── publisher.go │ ├── publisher_test.go │ ├── resolve.go │ ├── resolver.go │ ├── resolver_test.go │ ├── root.go │ ├── run.go │ └── version.go ├── doc.go ├── internal │ ├── git │ │ ├── clone.go │ │ ├── errors.go │ │ ├── git.go │ │ ├── info.go │ │ └── info_test.go │ ├── gittesting │ │ ├── git.go │ │ └── git_test.go │ └── testing │ │ ├── daemon.go │ │ ├── doc.go │ │ ├── fixed.go │ │ └── fixed_test.go ├── publish │ ├── daemon.go │ ├── daemon_test.go │ ├── default.go │ ├── default_test.go │ ├── doc.go │ ├── future.go │ ├── future_test.go │ ├── kind.go │ ├── kind │ │ ├── doc.go │ │ ├── write.go │ │ └── write_test.go │ ├── layout.go │ ├── layout_test.go │ ├── multi.go │ ├── multi_test.go │ ├── options.go │ ├── publish.go │ ├── recorder.go │ ├── recorder_test.go │ ├── shared.go │ ├── shared_test.go │ ├── tarball.go │ └── tarball_test.go └── resolve │ ├── doc.go │ ├── resolve.go │ ├── resolve_test.go │ ├── selector.go │ └── selector_test.go └── test ├── build-configs ├── .ko.yaml ├── bar │ ├── cmd │ │ └── main.go │ └── go.mod ├── caps.ko.yaml ├── caps │ ├── cmd │ │ └── main.go │ └── go.mod ├── foo │ ├── cmd │ │ └── main.go │ └── go.mod └── toolexec │ ├── cmd │ └── main.go │ └── go.mod ├── kodata ├── a ├── b └── kenobi ├── main.go └── test.yaml /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: github-actions 8 | directory: / 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/analyze.yaml: -------------------------------------------------------------------------------- 1 | name: Analyze 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: ['main'] 7 | pull_request: 8 | branches: ['main'] 9 | 10 | permissions: {} 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | permissions: 18 | security-events: write 19 | 20 | steps: 21 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 22 | with: 23 | # We must fetch at least the immediate parents so that if this is 24 | # a pull request then we can checkout the head. 25 | fetch-depth: 2 26 | persist-credentials: false 27 | 28 | - uses: github/codeql-action/init@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 29 | with: 30 | languages: go 31 | 32 | - uses: github/codeql-action/autobuild@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 33 | 34 | - uses: github/codeql-action/analyze@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 35 | -------------------------------------------------------------------------------- /.github/workflows/boilerplate.yaml: -------------------------------------------------------------------------------- 1 | name: Boilerplate 2 | 3 | on: 4 | pull_request: 5 | branches: ['main'] 6 | 7 | permissions: {} 8 | 9 | jobs: 10 | check: 11 | name: Boilerplate Check 12 | runs-on: ubuntu-latest 13 | 14 | permissions: 15 | contents: read 16 | 17 | strategy: 18 | fail-fast: false # Keep running if one leg fails. 19 | matrix: 20 | extension: 21 | - go 22 | - sh 23 | 24 | # Map between extension and human-readable name. 25 | include: 26 | - extension: go 27 | language: Go 28 | - extension: sh 29 | language: Bash 30 | 31 | steps: 32 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 33 | with: 34 | persist-credentials: false 35 | 36 | - uses: chainguard-dev/actions/boilerplate@e82b4e5ae10182af72972addcb3fedf7454621c8 # main 37 | with: 38 | extension: ${{ matrix.extension }} 39 | language: ${{ matrix.language }} 40 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - 'main' 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 17 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 18 | with: 19 | go-version: '1.24' 20 | check-latest: true 21 | 22 | - uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4 23 | with: 24 | go-version-input: '1.24' 25 | 26 | - run: | 27 | go build ./... 28 | go test -run=^$ ./... 29 | -------------------------------------------------------------------------------- /.github/workflows/donotsubmit.yaml: -------------------------------------------------------------------------------- 1 | name: Do Not Submit 2 | 3 | on: 4 | pull_request: 5 | branches: ['main'] 6 | 7 | permissions: {} 8 | 9 | jobs: 10 | donotsubmit: 11 | name: Do Not Submit 12 | runs-on: ubuntu-latest 13 | 14 | permissions: 15 | contents: read 16 | 17 | steps: 18 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 19 | with: 20 | persist-credentials: false 21 | 22 | - uses: chainguard-dev/actions/donotsubmit@84c993eaf02da1c325854fb272a4df9184bd80fc # main 23 | -------------------------------------------------------------------------------- /.github/workflows/image.yaml: -------------------------------------------------------------------------------- 1 | name: image 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | workflow_dispatch: 8 | 9 | permissions: {} 10 | 11 | jobs: 12 | image: 13 | runs-on: ubuntu-latest 14 | 15 | permissions: 16 | contents: read 17 | packages: write 18 | id-token: write 19 | 20 | steps: 21 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 22 | with: 23 | persist-credentials: false 24 | 25 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 26 | with: 27 | go-version-file: 'go.mod' 28 | check-latest: true 29 | - uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 30 | 31 | # Build ko from HEAD, build and push an image tagged with the commit SHA, 32 | # then keylessly sign it with cosign. 33 | - name: Publish and sign image 34 | env: 35 | KO_DOCKER_REPO: ghcr.io/${{ github.repository }} 36 | run: | 37 | go build ./ 38 | echo "${{ github.token }}" | ./ko login ghcr.io --username "${{ github.actor }}" --password-stdin 39 | img=$(./ko build --bare --platform=all -t latest -t ${{ github.sha }} ./) 40 | echo "built ${img}" 41 | cosign sign ${img} --yes \ 42 | -a sha=${{ github.sha }} \ 43 | -a run_id=${{ github.run_id }} \ 44 | -a run_attempt=${{ github.run_attempt }} 45 | -------------------------------------------------------------------------------- /.github/workflows/kind-e2e.yaml: -------------------------------------------------------------------------------- 1 | name: KinD e2e tests 2 | 3 | on: 4 | workflow_dispatch: # Allow manual runs. 5 | pull_request: 6 | branches: 7 | - 'main' 8 | 9 | permissions: {} 10 | 11 | jobs: 12 | e2e-tests: 13 | name: e2e tests 14 | runs-on: ubuntu-latest 15 | env: 16 | # https://github.com/google/go-containerregistry/pull/125 allows insecure registry for 17 | # '*.local' hostnames. This works both for `ko` and our own tag-to-digest resolution logic, 18 | # thus allowing us to test without bypassing tag-to-digest resolution. 19 | REGISTRY_NAME: registry.local 20 | REGISTRY_PORT: 5000 21 | KO_DOCKER_REPO: registry.local:5000/ko 22 | 23 | permissions: 24 | contents: read 25 | 26 | steps: 27 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 28 | with: 29 | persist-credentials: false 30 | 31 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 32 | with: 33 | go-version-file: 'go.mod' 34 | check-latest: true 35 | 36 | - name: Install ko 37 | run: go install ./ 38 | 39 | - name: Setup Cluster 40 | uses: chainguard-dev/actions/setup-kind@29fb6e979a0b3efc79748a17e8cec08d0594cbfd # main 41 | with: 42 | k8s-version: v1.28.x 43 | registry-authority: ${{ env.REGISTRY_NAME }}:${{ env.REGISTRY_PORT }} 44 | 45 | - name: Install Cosign 46 | uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 47 | 48 | - name: Run Smoke Test 49 | run: | 50 | # Test with kind load 51 | KO_DOCKER_REPO=kind.local ko apply --platform=all -f ./test 52 | kubectl wait --timeout=10s --for=condition=Ready pod/kodata 53 | kubectl delete pod kodata 54 | 55 | # Test with registry 56 | ko apply --platform=all -f ./test 57 | kubectl wait --timeout=60s --for=condition=Ready pod/kodata 58 | kubectl delete pod kodata 59 | 60 | # Test ko run with kind load 61 | # This tests that --labels are passed to kubectl, and -wait is passed to the test binary. 62 | KO_DOCKER_REPO=kind.local ko run ./test -- --labels=foo=bar -- -wait=false 63 | 64 | - name: Check SBOM 65 | run: | 66 | set -o pipefail 67 | 68 | echo '::group:: SBOM' 69 | cosign download sbom $(ko build ./test) 70 | echo "${SBOM}" 71 | echo '::endgroup::' 72 | 73 | - name: Collect diagnostics and upload 74 | if: ${{ failure() }} 75 | uses: chainguard-dev/actions/kind-diag@29fb6e979a0b3efc79748a17e8cec08d0594cbfd # main 76 | -------------------------------------------------------------------------------- /.github/workflows/modules-integration-test.yaml: -------------------------------------------------------------------------------- 1 | name: Integration Test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - 'main' 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | test: 12 | name: Module Tests 13 | runs-on: 'ubuntu-latest' 14 | 15 | permissions: 16 | contents: read 17 | 18 | steps: 19 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 20 | with: 21 | persist-credentials: false 22 | 23 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 24 | with: 25 | go-version: '1.24' 26 | check-latest: true 27 | 28 | - env: 29 | GOPATH: does not matter 30 | run: ./integration_test.sh 31 | -------------------------------------------------------------------------------- /.github/workflows/publish-site.yaml: -------------------------------------------------------------------------------- 1 | name: publish 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - 'main' 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-latest 13 | 14 | permissions: 15 | contents: write 16 | pages: write 17 | 18 | steps: 19 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 20 | - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 21 | with: 22 | python-version: 3.x 23 | - run: pip install mkdocs-material mkdocs-redirects 24 | - run: mkdocs gh-deploy --force 25 | -------------------------------------------------------------------------------- /.github/workflows/registries.yaml: -------------------------------------------------------------------------------- 1 | name: Push to registries 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | 8 | workflow_dispatch: # Allow manual runs. 9 | 10 | permissions: {} 11 | 12 | jobs: 13 | quay: 14 | name: Push to quay.io 15 | runs-on: ubuntu-latest 16 | 17 | permissions: 18 | contents: read 19 | 20 | steps: 21 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 22 | with: 23 | persist-credentials: false 24 | 25 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 26 | with: 27 | go-version-file: 'go.mod' 28 | check-latest: true 29 | 30 | - env: 31 | QUAY_USERNAME: ko-testing+test 32 | QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }} 33 | KO_DOCKER_REPO: quay.io/ko-testing/test 34 | run: | 35 | echo ${QUAY_PASSWORD} | go run ./ login --username=${QUAY_USERNAME} --password-stdin quay.io 36 | go run ./ build --platform=all ./test/ --sbom=none --bare 37 | 38 | dockerhub: 39 | name: Push to dockerhub 40 | runs-on: ubuntu-latest 41 | 42 | permissions: 43 | contents: read 44 | 45 | steps: 46 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 47 | with: 48 | persist-credentials: false 49 | 50 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 51 | with: 52 | go-version-file: 'go.mod' 53 | check-latest: true 54 | 55 | - env: 56 | DOCKERHUB_USERNAME: kotesting 57 | DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} 58 | KO_DOCKER_REPO: kotesting/test 59 | run: | 60 | echo ${DOCKERHUB_PASSWORD} | go run ./ login --username=${DOCKERHUB_USERNAME} --password-stdin index.docker.io 61 | go run ./ build --platform=all ./test/ --bare 62 | 63 | ecr: 64 | name: Push to ECR 65 | runs-on: ubuntu-latest 66 | env: 67 | # This is an AWS account that Chainguard provides to enable 68 | # go-containerregistry and ko to test ECR support. 69 | AWS_ACCOUNT: 479305788615 70 | AWS_REGION: us-west-2 71 | REPOSITORY: ko-ecr-e2e-testing 72 | 73 | permissions: 74 | # This lets us clone the repo 75 | contents: read 76 | # This lets us mint identity tokens for federation with AWS. 77 | id-token: write 78 | 79 | steps: 80 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 81 | with: 82 | persist-credentials: false 83 | 84 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 85 | with: 86 | go-version-file: 'go.mod' 87 | check-latest: true 88 | 89 | - name: Install ko 90 | run: go install . 91 | 92 | - name: Configure AWS Credentials 93 | uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 94 | with: 95 | role-to-assume: arn:aws:iam::${{ env.AWS_ACCOUNT }}:role/federated-ecr-readwrite 96 | aws-region: ${{ env.AWS_REGION }} 97 | 98 | - name: Test ko build 99 | run: | 100 | export KO_DOCKER_REPO=${{ env.AWS_ACCOUNT }}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com/${{ env.REPOSITORY }} 101 | 102 | ko build --bare ./test 103 | -------------------------------------------------------------------------------- /.github/workflows/sbom.yaml: -------------------------------------------------------------------------------- 1 | name: Validate SBOMs 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - 'main' 7 | 8 | env: 9 | SPDX_TOOLS_VERSION: 1.1.0 10 | 11 | permissions: {} 12 | 13 | jobs: 14 | spdx: 15 | name: Validate SPDX SBOM 16 | runs-on: ubuntu-latest 17 | 18 | permissions: 19 | contents: read 20 | 21 | env: 22 | KO_DOCKER_REPO: localhost:1338 23 | 24 | steps: 25 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | with: 27 | persist-credentials: false 28 | 29 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 30 | with: 31 | go-version-file: 'go.mod' 32 | check-latest: true 33 | 34 | - uses: chainguard-dev/actions/setup-registry@main 35 | - uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 36 | 37 | - name: Install SPDX Tools 38 | run: | 39 | wget https://github.com/spdx/tools-java/releases/download/v${SPDX_TOOLS_VERSION}/tools-java-${SPDX_TOOLS_VERSION}.zip 40 | unzip tools-java-${SPDX_TOOLS_VERSION}.zip 41 | 42 | - name: Generate and Validate 43 | run: | 44 | cosign download sbom $(go run ./ build) | tee spdx.json 45 | java -jar ./tools-java-${SPDX_TOOLS_VERSION}-jar-with-dependencies.jar Verify spdx.json 46 | 47 | - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 48 | if: ${{ always() }} 49 | with: 50 | name: spdx.json 51 | path: spdx.json 52 | 53 | spdx-multi-arch: 54 | name: Validate SPDX multi-arch SBOM 55 | runs-on: ubuntu-latest 56 | 57 | env: 58 | KO_DOCKER_REPO: localhost:1338 59 | 60 | permissions: 61 | contents: read 62 | 63 | steps: 64 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 65 | with: 66 | persist-credentials: false 67 | 68 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 69 | with: 70 | go-version-file: 'go.mod' 71 | check-latest: true 72 | - uses: chainguard-dev/actions/setup-registry@main 73 | - uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 74 | 75 | - name: Install SPDX Tools 76 | run: | 77 | wget https://github.com/spdx/tools-java/releases/download/v${SPDX_TOOLS_VERSION}/tools-java-${SPDX_TOOLS_VERSION}.zip 78 | unzip tools-java-${SPDX_TOOLS_VERSION}.zip 79 | 80 | - name: Generate and Validate 81 | run: | 82 | img=$(go run ./ build --platform=linux/amd64,linux/arm64) 83 | cosign download sbom $img | tee spdx-multi-arch.json 84 | 85 | java -jar ./tools-java-${SPDX_TOOLS_VERSION}-jar-with-dependencies.jar Verify spdx-multi-arch.json 86 | 87 | - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 88 | if: ${{ always() }} 89 | with: 90 | name: spdx-multi-arch.json 91 | path: spdx-multi-arch.json 92 | -------------------------------------------------------------------------------- /.github/workflows/stale.yaml: -------------------------------------------------------------------------------- 1 | name: 'Close stale' 2 | 3 | on: 4 | schedule: 5 | - cron: '0 1 * * *' 6 | 7 | permissions: {} 8 | 9 | jobs: 10 | stale: 11 | runs-on: 'ubuntu-latest' 12 | 13 | permissions: 14 | issues: write 15 | pull-requests: write 16 | 17 | steps: 18 | - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 19 | with: 20 | repo-token: ${{ secrets.GITHUB_TOKEN }} 21 | 22 | stale-issue-message: |- 23 | This issue is stale because it has been open for 90 days with no 24 | activity. It will automatically close after 30 more days of 25 | inactivity. Keep fresh with the 'lifecycle/frozen' label. 26 | stale-issue-label: 'lifecycle/stale' 27 | exempt-issue-labels: 'lifecycle/frozen' 28 | 29 | stale-pr-message: |- 30 | This Pull Request is stale because it has been open for 90 days with 31 | no activity. It will automatically close after 30 more days of 32 | inactivity. Keep fresh with the 'lifecycle/frozen' label. 33 | stale-pr-label: 'lifecycle/stale' 34 | exempt-pr-labels: 'lifecycle/frozen' 35 | 36 | days-before-stale: 90 37 | days-before-close: 30 38 | -------------------------------------------------------------------------------- /.github/workflows/style.yaml: -------------------------------------------------------------------------------- 1 | name: Code Style 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - 'main' 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | gofmt: 12 | name: check gofmt 13 | runs-on: ubuntu-latest 14 | 15 | permissions: 16 | contents: read 17 | 18 | steps: 19 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 20 | with: 21 | persist-credentials: false 22 | 23 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 24 | with: 25 | go-version-file: 'go.mod' 26 | check-latest: true 27 | 28 | - uses: chainguard-dev/actions/gofmt@d886686603afb809f7ef9b734b333e20b7ce5cda 29 | with: 30 | args: -s 31 | 32 | goimports: 33 | name: check goimports 34 | runs-on: ubuntu-latest 35 | 36 | permissions: 37 | contents: read 38 | 39 | steps: 40 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 41 | with: 42 | persist-credentials: false 43 | 44 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 45 | with: 46 | go-version-file: 'go.mod' 47 | check-latest: true 48 | 49 | - uses: chainguard-dev/actions/goimports@d886686603afb809f7ef9b734b333e20b7ce5cda 50 | 51 | lint: 52 | name: Lint 53 | runs-on: ubuntu-latest 54 | 55 | permissions: 56 | contents: read 57 | 58 | steps: 59 | - name: Check out code 60 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 61 | with: 62 | persist-credentials: false 63 | 64 | - name: Set up Go 65 | uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 66 | with: 67 | go-version-file: 'go.mod' 68 | check-latest: true 69 | 70 | - uses: chainguard-dev/actions/trailing-space@d886686603afb809f7ef9b734b333e20b7ce5cda 71 | if: ${{ always() }} 72 | 73 | - uses: chainguard-dev/actions/eof-newline@d886686603afb809f7ef9b734b333e20b7ce5cda 74 | if: ${{ always() }} 75 | 76 | - uses: reviewdog/action-misspell@18ffb61effb93b47e332f185216be7e49592e7e1 # v1.26.1 77 | if: ${{ always() }} 78 | with: 79 | github_token: ${{ secrets.GITHUB_TOKEN }} 80 | fail_level: warning 81 | locale: "US" 82 | exclude: | 83 | ./.golangci.yaml 84 | 85 | - uses: get-woke/woke-action-reviewdog@d71fd0115146a01c3181439ce714e21a69d75e31 # v0 86 | if: ${{ always() }} 87 | with: 88 | github-token: ${{ secrets.GITHUB_TOKEN }} 89 | reporter: github-pr-check 90 | level: error 91 | fail-on-error: true 92 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | pull_request: 8 | branches: 9 | - 'main' 10 | 11 | permissions: {} 12 | 13 | jobs: 14 | 15 | test: 16 | name: Unit Tests 17 | runs-on: ubuntu-latest 18 | 19 | permissions: 20 | contents: read 21 | 22 | steps: 23 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 24 | with: 25 | persist-credentials: false 26 | 27 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 28 | with: 29 | go-version-file: 'go.mod' 30 | check-latest: true 31 | 32 | - run: go test -coverprofile=coverage.txt -covermode=atomic -race ./... 33 | -------------------------------------------------------------------------------- /.github/workflows/verify.yaml: -------------------------------------------------------------------------------- 1 | name: Verify 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - 'main' 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | verify: 12 | name: Verify Codegen 13 | runs-on: ubuntu-latest 14 | 15 | permissions: 16 | contents: read 17 | 18 | steps: 19 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 20 | with: 21 | persist-credentials: false 22 | 23 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 24 | with: 25 | go-version-file: 'go.mod' 26 | check-latest: true 27 | 28 | - name: Verify 29 | run: ./hack/presubmit.sh 30 | 31 | golangci: 32 | name: lint 33 | runs-on: ubuntu-latest 34 | 35 | permissions: 36 | contents: read 37 | 38 | steps: 39 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 40 | with: 41 | persist-credentials: false 42 | 43 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 44 | with: 45 | go-version-file: 'go.mod' 46 | check-latest: true 47 | 48 | - name: golangci-lint 49 | uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd # v7.0.0 50 | with: 51 | version: v2.0 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore GoLand (IntelliJ) files. 2 | .idea/ 3 | 4 | ko 5 | 6 | .DS_Store 7 | 8 | /vendor/ 9 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | enable: 4 | - asciicheck 5 | - errorlint 6 | - gosec 7 | - importas 8 | - misspell 9 | - prealloc 10 | - revive 11 | - staticcheck 12 | - tparallel 13 | - unconvert 14 | - unparam 15 | - whitespace 16 | disable: 17 | - depguard 18 | - errcheck 19 | settings: 20 | gosec: 21 | excludes: 22 | - G115 23 | exclusions: 24 | generated: lax 25 | presets: 26 | - comments 27 | - common-false-positives 28 | - legacy 29 | - std-error-handling 30 | rules: 31 | - linters: 32 | - gosec 33 | path: test 34 | paths: 35 | - third_party$ 36 | - builtin$ 37 | - examples$ 38 | formatters: 39 | enable: 40 | - gofmt 41 | - goimports 42 | exclusions: 43 | generated: lax 44 | paths: 45 | - third_party$ 46 | - builtin$ 47 | - examples$ 48 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | before: 4 | hooks: 5 | - go mod tidy 6 | - /bin/bash -c 'if [ -n "$(git --no-pager diff --exit-code go.mod go.sum)" ]; then exit 1; fi' 7 | 8 | builds: 9 | - id: binary 10 | main: ./main.go 11 | env: 12 | - CGO_ENABLED=0 13 | flags: 14 | - -trimpath 15 | ldflags: 16 | - "-s -w -X github.com/google/ko/pkg/commands.Version={{.Version}}" 17 | goos: 18 | - windows 19 | - linux 20 | - darwin 21 | goarch: 22 | - amd64 23 | - arm64 24 | - s390x 25 | - 386 26 | - mips64le 27 | - ppc64le 28 | - riscv64 29 | 30 | kos: 31 | - id: ko-image 32 | build: binary 33 | main: . 34 | base_image: golang:latest 35 | ldflags: 36 | - "-s -w -X github.com/google/ko/pkg/commands.Version={{.Version}}" 37 | platforms: 38 | - all 39 | tags: 40 | - '{{ .Tag }}' 41 | - '{{ .FullCommit }}' 42 | - latest 43 | sbom: spdx 44 | bare: true 45 | preserve_import_paths: false 46 | base_import_paths: false 47 | 48 | archives: 49 | - id: with-version 50 | name_template: >- 51 | {{ .ProjectName }}_ 52 | {{- .Version }}_ 53 | {{- title .Os }}_ 54 | {{- if eq .Arch "amd64" }}x86_64 55 | {{- else if eq .Arch "386" }}i386 56 | {{- else }}{{ .Arch }}{{ end }} 57 | - id: without-version 58 | name_template: >- 59 | {{ .ProjectName }}_ 60 | {{- title .Os }}_ 61 | {{- if eq .Arch "amd64" }}x86_64 62 | {{- else if eq .Arch "386" }}i386 63 | {{- else }}{{ .Arch }}{{ end }} 64 | 65 | checksum: 66 | name_template: 'checksums.txt' 67 | 68 | snapshot: 69 | version_template: "{{ .Tag }}-next" 70 | 71 | changelog: 72 | sort: asc 73 | use: github 74 | filters: 75 | exclude: 76 | - '^docs:' 77 | - '^test:' 78 | -------------------------------------------------------------------------------- /.ko.yaml: -------------------------------------------------------------------------------- 1 | baseImageOverrides: 2 | github.com/google/ko: golang:latest 3 | 4 | builds: 5 | - id: ko 6 | ldflags: 7 | - "{{ .Env.LDFLAGS }}" 8 | -------------------------------------------------------------------------------- /.wokeignore: -------------------------------------------------------------------------------- 1 | # Uses some Cobra methods 2 | pkg/commands/* 3 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | ko.build -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute to ko 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Code reviews 7 | 8 | All submissions, including submissions by project members, require review. We 9 | use GitHub pull requests for this purpose. Consult 10 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 11 | information on using pull requests. 12 | 13 | ## Testing 14 | 15 | Ensure the following passes: 16 | ``` 17 | ./hack/presubmit.sh 18 | ``` 19 | and commit any resultant changes to `go.mod` and `go.sum`. To update any docs 20 | after client changes, run: 21 | 22 | ``` 23 | ./hack/update-codegen.sh 24 | ``` 25 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | This page lists all active members of the maintainers for the `ko` project and any subprojects. 4 | 5 | - Jon Johnson (@jonjohnsonjr) 6 | - Matt Moore (@mattmoor) 7 | - Jason Hall (@imjasonh) 8 | 9 | New projects and new maintainers can be added or removed with the consensus approval of the current maintainers. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `ko`: Easy Go Containers 2 | 3 | [![GitHub Actions Build Status](https://github.com/ko-build/ko/workflows/Build/badge.svg)](https://github.com/ko-build/ko/actions?query=workflow%3ABuild) 4 | [![GoDoc](https://godoc.org/github.com/google/ko?status.svg)](https://godoc.org/github.com/google/ko) 5 | [![Go Report Card](https://goreportcard.com/badge/ko-build/ko)](https://goreportcard.com/report/ko-build/ko) 6 | [![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev/images/gh-badge-level3.svg) 7 | 8 | 9 | 10 | --- 11 | 12 | > 🎉 Google has applied for `ko` to join the Cloud Native Computing Foundation as a Sandbox project! Learn more [here](https://opensource.googleblog.com/2022/10/ko-applies-to-become-a-cncf-sandbox-project.html)! 13 | 14 | `ko` is a simple, fast container image builder for Go applications. 15 | 16 | It's ideal for use cases where your image contains a single Go application 17 | without any/many dependencies on the OS base image (e.g., no cgo, no OS package 18 | dependencies). 19 | 20 | `ko` builds images by effectively executing `go build` on your local machine, 21 | and as such doesn't require `docker` to be installed. This can make it a good 22 | fit for lightweight CI/CD use cases. 23 | 24 | `ko` makes [multi-platform builds](https://ko.build/features/multi-platform/) easy, produces [SBOMs](https://ko.build/features/sboms/) by default, and includes support for simple YAML templating which makes it a powerful tool for [Kubernetes applications](https://ko.build/features/k8s/). 25 | 26 | # [Install `ko`](https://ko.build/install/) and [get started](https://ko.build/get-started/)! 27 | 28 | ### Acknowledgements 29 | 30 | This work is based heavily on experience from having built the [Docker](https://github.com/bazelbuild/rules_docker) and [Kubernetes](https://github.com/bazelbuild/rules_k8s) support for [Bazel](https://bazel.build). 31 | That work was presented [here](https://www.youtube.com/watch?v=RS1aiQqgUTA). 32 | 33 | ### Discuss 34 | 35 | Questions? Comments? Ideas? 36 | Come discuss `ko` with us in the `#ko-build` channel on the [Kubernetes Slack](https://slack.k8s.io)! 37 | See you there! 38 | 39 | ### Community Meetings 40 | 41 | You can find all the necessary details about the community meetings in this [page](https://ko.build/community). 42 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # `ko` Project Roadmap 2 | 3 | _Last updated October 2022_ 4 | 5 | - Foster a community of contributors and users 6 | - give talks, do outreach, expand the pool of contributors 7 | - identify projects that could benefit from using `ko`, and help onboard them 8 | - publish case studies from successful migrations 9 | 10 | - Integrate [sigstore](https://sigstore.dev) for built artifacts 11 | - attach signed SBOMs 12 | - attach signed provenance attestations 13 | - support the OCI referrers API and [fallback tag scheme](https://github.com/opencontainers/distribution-spec/blob/main/spec.md#referrers-tag-schema) 14 | - integrate with CI workload identity (e.g., GitHub OIDC) to keylessly sign artifacts 15 | 16 | - Faster builds 17 | - identify unnecessary work and avoid it when possible 18 | 19 | - Ecosystem integrations 20 | - support Terraform provider, and potentially Pulumi and CDK, others 21 | - provide working examples of these integrations 22 | -------------------------------------------------------------------------------- /cmd/help/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/google/ko/pkg/commands" 22 | "github.com/spf13/cobra" 23 | "github.com/spf13/cobra/doc" 24 | ) 25 | 26 | var dir string 27 | var root = &cobra.Command{ 28 | Use: "gendoc", 29 | Short: "Generate ko's help docs", 30 | Args: cobra.NoArgs, 31 | RunE: func(*cobra.Command, []string) error { 32 | return doc.GenMarkdownTree(commands.Root, dir) 33 | }, 34 | } 35 | 36 | func init() { 37 | root.Flags().StringVarP(&dir, "dir", "d", ".", "Path to directory in which to generate docs") 38 | } 39 | 40 | func main() { 41 | if err := root.Execute(); err != nil { 42 | fmt.Println(err) 43 | os.Exit(1) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | ko.build 2 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Docs for https://ko.build 2 | 3 | ## Development 4 | 5 | Update `.md` files to update content. 6 | 7 | Update `mkdocs.yml` to update sidebar headers and ordering. 8 | 9 | To run locally: 10 | 11 | - [install `mkdocs` and `mkdocs-material`](https://squidfunk.github.io/mkdocs-material/getting-started/) and run `mkdocs serve`, or 12 | - `docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material` 13 | - on an M1 Mac, use `ghcr.io/afritzler/mkdocs-material` instead. 14 | 15 | This will start a local server on localhost:8000 that autoupdates as you make changes. 16 | 17 | ## Deployment 18 | 19 | When PRs are merged, the site will be rebuilt and published automatically. 20 | 21 | ### Credits 22 | 23 | The site is powered by [mkdocs-material](https://squidfunk.github.io/mkdocs-material). The code and theme are released under the MIT license. 24 | 25 | Content is licensed [CC-BY](https://creativecommons.org/licenses/by/4.0/). 26 | 27 | -------------------------------------------------------------------------------- /docs/advanced/go-packages.md: -------------------------------------------------------------------------------- 1 | # Go Packages 2 | 3 | `ko`'s functionality can be consumed as a library in a Go application. 4 | 5 | To build an image, use [`pkg/build`](https://pkg.go.dev/github.com/google/ko/pkg/build), and publish it with [`pkg/publish`](https://pkg.go.dev/github.com/google/ko/pkg/publish). 6 | 7 | This is a minimal example of using the packages together, to implement the core subset of `ko`'s functionality: 8 | 9 | ```go 10 | package main 11 | 12 | import ( 13 | "context" 14 | "fmt" 15 | "log" 16 | 17 | "github.com/google/go-containerregistry/pkg/authn" 18 | "github.com/google/go-containerregistry/pkg/name" 19 | "github.com/google/go-containerregistry/pkg/v1/remote" 20 | "github.com/google/ko/pkg/build" 21 | "github.com/google/ko/pkg/publish" 22 | ) 23 | 24 | const ( 25 | baseImage = "cgr.dev/chainguard/static:latest" 26 | targetRepo = "example.registry/my-repo" 27 | importpath = "github.com/my-org/miniko" 28 | commitSHA = "deadbeef" 29 | ) 30 | 31 | func main() { 32 | ctx := context.Background() 33 | 34 | b, err := build.NewGo(ctx, ".", 35 | build.WithPlatforms("linux/amd64"), // only build for these platforms. 36 | build.WithBaseImages(func(ctx context.Context, _ string) (name.Reference, build.Result, error) { 37 | ref := name.MustParseReference(baseImage) 38 | base, err := remote.Index(ref, remote.WithContext(ctx)) 39 | return ref, base, err 40 | })) 41 | if err != nil { 42 | log.Fatalf("NewGo: %v", err) 43 | } 44 | r, err := b.Build(ctx, importpath) 45 | if err != nil { 46 | log.Fatalf("Build: %v", err) 47 | } 48 | 49 | p, err := publish.NewDefault(targetRepo, // publish to example.registry/my-repo 50 | publish.WithTags([]string{commitSHA}), // tag with :deadbeef 51 | publish.WithAuthFromKeychain(authn.DefaultKeychain)) // use credentials from ~/.docker/config.json 52 | if err != nil { 53 | log.Fatalf("NewDefault: %v", err) 54 | } 55 | ref, err := p.Publish(ctx, r, importpath) 56 | if err != nil { 57 | log.Fatalf("Publish: %v", err) 58 | } 59 | fmt.Println(ref.String()) 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/advanced/lambda.md: -------------------------------------------------------------------------------- 1 | # AWS Lambda 2 | 3 | `ko` can build images that can be deployed as AWS Lambda functions, using [Lambda's container support](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html). 4 | 5 | For best results, use the [Go runtime interface client](https://docs.aws.amazon.com/lambda/latest/dg/go-image.html#go-image-clients) provided by the [`lambda` package](https://pkg.go.dev/github.com/aws/aws-lambda-go/lambda). 6 | 7 | For example: 8 | 9 | ```go 10 | package main 11 | 12 | import ( 13 | "context" 14 | "fmt" 15 | "github.com/aws/aws-lambda-go/lambda" 16 | ) 17 | 18 | type Event struct { 19 | Name string `json:"name"` 20 | // TODO: add other request fields here. 21 | } 22 | 23 | func main() { 24 | lambda.Start(func(ctx context.Context, event Event) (string, error) { 25 | return fmt.Sprintf("Hello %s!", event.Name), nil 26 | }) 27 | } 28 | ``` 29 | 30 | See AWS's [documentation](https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html) for more information on writing Lambda functions in Go. 31 | 32 | To deploy to Lambda, you must push to AWS Elastic Container Registry (ECR): 33 | 34 | ```sh 35 | KO_DOCKER_REPO=[account-id].dkr.ecr.[region].amazonaws.com/my-repo 36 | image=$(ko build ./cmd/app) 37 | ``` 38 | 39 | Then, create a Lambda function using the image in ECR: 40 | 41 | ```sh 42 | aws lambda create-function \ 43 | --function-name hello-world \ 44 | --package-type Image \ 45 | --code ImageUri=${image} \ 46 | --role arn:aws:iam::[account-id]:role/lambda-ex 47 | ``` 48 | 49 | See AWS's [documentation](https://docs.aws.amazon.com/lambda/latest/dg/go-image.html) for more information on deploying Lambda functions using Go container images, including how to configure push access to ECR, and how to configure the IAM role for the function. 50 | 51 | The base image that `ko` uses by default supports both x86 and Graviton2 architectures. 52 | 53 | You can also use the [`ko` Terraform provider](./terraform.md) to build and deploy Lambda functions as part of your IaC workflow, using the [`aws_lambda_function` resource](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function.html). See the [provider example](https://github.com/ko-build/terraform-provider-ko/tree/main/provider-examples/lambda) to get started. 54 | -------------------------------------------------------------------------------- /docs/advanced/limitations.md: -------------------------------------------------------------------------------- 1 | # Limitations 2 | 3 | `ko` works best when your application has no dependencies on the underlying image. 4 | 5 | This means `ko` is ideal when you don't require [cgo](https://pkg.go.dev/cmd/cgo), and builds are executed with `CGO_ENABLED=0` by default. 6 | 7 | To install other OS packages, make those available in your [configured base image](../../configuration). 8 | 9 | `ko` only supports Go applications. 10 | For a similar tool targeting Java applications, try [Jib](https://github.com/GoogleContainerTools/jib). 11 | For other languages, try [apko](https://github.com/chainguard-dev/apko) and [melange](https://github.com/chainguard-dev/melange). 12 | 13 | -------------------------------------------------------------------------------- /docs/advanced/linux-capabilities.md: -------------------------------------------------------------------------------- 1 | # Linux Capabilities 2 | 3 | In Linux, capabilities are a way to selectively grant privileges to a running process. 4 | 5 | Docker provides `--cap-add` and `--cap-drop` 6 | [run options](https://docs.docker.com/engine/containers/run/#runtime-privilege-and-linux-capabilities) 7 | to tweak container capabilities, e.g: 8 | 9 | ``` 10 | docker run --cap-add bpf hello-world 11 | ``` 12 | 13 | If container runs as a non-root user, 14 | capabilities are narrowed by intersecting with *file* capabilities of the 15 | application binary. When building images with a Dockerfile, one 16 | typically uses `setcap` tool to modify file capabilities, e.g: 17 | `setcap FILE bpf=ep`. 18 | 19 | To set file capabilities with `ko`, specify `linux_capabilities` 20 | in builds configuration section in your `.ko.yaml`. Use `setcap` syntax: 21 | 22 | ```yaml 23 | builds: 24 | - id: caps 25 | linux_capabilities: bpf=ep 26 | ``` 27 | ## Alternative spelling 28 | 29 | ```yaml 30 | builds: 31 | - id: caps 32 | linux_capabilities: 33 | - cap1 34 | - cap2 35 | - cap3 36 | ``` 37 | 38 | A list of capability names is equivalent to `cap1,cap2,cap3=p`. 39 | 40 | ## Improving UX in capability-reliant apps 41 | 42 | A capability can be *permitted* (`=p`), or both *permitted* and *effective* (`=ep`). 43 | Effective capabilities are used for permission checks. 44 | A program can promote permitted capability to effective when needed. 45 | 46 | ```yaml 47 | builds: 48 | - id: caps 49 | linux_capabilities: bpf,perfmon,net_admin=ep 50 | ``` 51 | 52 | Initially, `=ep` might look like a good idea. 53 | There's no need to explicitly promote *permitted* capabilities. 54 | Application takes advantage of *effective* capabilities right away. 55 | 56 | There is a catch though. 57 | 58 | ``` 59 | $ docker run --cap-add bpf ko.local/caps.test-4b8f7bca75c467b3d2803e1c087a3287 60 | exec /ko-app/caps.test: operation not permitted 61 | ``` 62 | 63 | When run options request fewer capabilities than specified in file capabilities, 64 | container fails to start. It is hard to tell what went wrong since 65 | `operation not permitted` is a generic error. (Docker is unlikely to improve diagnostics 66 | for this failure case since the check is implemented in Linux kernel.) 67 | 68 | 69 | We suggest to use `=p` instead. This option puts application in charge of verifying and 70 | promoting permitted capabilities to effective. It can produce much better diagnostics: 71 | 72 | ``` 73 | $ docker run --cap-add bpf ko.local/caps.test-4b8f7bca75c467b3d2803e1c087a3287 74 | current capabilities: cap_bpf=p 75 | activating capabilities: cap_net_admin,cap_perfmon,cap_bpf=ep: operation not permitted 76 | ``` 77 | 78 | [Sample code](https://go.dev/play/p/uPMzyotkNHg). 79 | -------------------------------------------------------------------------------- /docs/advanced/migrating-from-dockerfile.md: -------------------------------------------------------------------------------- 1 | # Migrating from Dockerfile 2 | 3 | If your `Dockerfile` looks like either of the examples in the [official tutorial for writing a Dockerfile to containerize a Go application](https://docs.docker.com/language/golang/build-images/), you can easily migrate to use `ko` instead. 4 | 5 | Let's review the best practice multi-stage Dockerfile in that tutorial first: 6 | 7 | ```Dockerfile 8 | ## Build 9 | FROM golang:1.16-buster AS build 10 | 11 | WORKDIR /app 12 | 13 | COPY go.mod ./ 14 | COPY go.sum ./ 15 | RUN go mod download 16 | 17 | COPY *.go ./ 18 | 19 | RUN go build -o /docker-gs-ping 20 | 21 | ## Deploy 22 | FROM gcr.io/distroless/base-debian10 23 | 24 | WORKDIR / 25 | 26 | COPY --from=build /docker-gs-ping /docker-gs-ping 27 | 28 | EXPOSE 8080 29 | 30 | USER nonroot:nonroot 31 | 32 | ENTRYPOINT ["/docker-gs-ping"] 33 | ``` 34 | 35 | This `Dockerfile`: 36 | 37 | 1. pulls the `golang:1.16` image 38 | 1. `COPY`s your local source into the container environment (`COPY`ing `go.mod` and `go.sum` first and running `go mod download`, to cache dependencies in the container environment) 39 | 1. `RUN`s `go build` on your source, inside the container, to produce an executable 40 | 1. `COPY`s the executable built in the previous step into a new image, on top of a minimal [distroless](https://github.com/GoogleContainerTools/distroless) base image. 41 | 42 | The result is a Go application built on a minimal base image, with an optimally cached build sequence. 43 | 44 | After running `docker build` on this `Dockerfile`, don't forget to push that image to the registry so you can deploy it. 45 | 46 | --- 47 | 48 | ## Migrating to `ko` 49 | 50 | If your Go source is laid out as described in the tutorial, and you've [installed](../../install) and [set up your environment](../../get-started), you can simply run `ko build ./` to build and push the container image to your registry. 51 | 52 | You're done. You can delete your `Dockerfile` and uninstall `docker`. 53 | 54 | `ko` takes advantage of your local [Go build cache](../../features/build-cache) without needing to be told to, and it sets the `ENTRYPOINT` and uses a nonroot distroless base image by default. 55 | 56 | To build a multi-arch image, simply add `--platform=all`. 57 | Compare this to the [equivalent Docker instructions](https://docs.docker.com/desktop/multi-arch/). 58 | 59 | -------------------------------------------------------------------------------- /docs/advanced/terraform.md: -------------------------------------------------------------------------------- 1 | # Terraform Provider 2 | 3 | In addition to the CLI, `ko`'s functionality is also available as a Terraform provider. 4 | 5 | This allows `ko` to be integrated with your Infrastructure-as-Code (IaC) workflows, and makes building your code a seamless part of your deployment process. 6 | 7 | Using the Terraform provider is as simple as adding a `ko_build` resource to your Terraform configuration: 8 | 9 | ```hcl 10 | // Require the `ko-build/ko` provider. 11 | terraform { 12 | required_providers { 13 | ko = { source = "ko-build/ko" } 14 | } 15 | } 16 | 17 | // Configure the provider to push to your repo. 18 | provider "ko" { 19 | repo = "example.registry/my-repo" // equivalent to KO_DOCKER_REPO 20 | } 21 | 22 | // Build your code. 23 | resource "ko_build" "app" { 24 | importpath = "github.com/example/repo/cmd/app" 25 | } 26 | 27 | // TODO: use the `ko_build.app` resource elsewhere in your Terraform configuration. 28 | 29 | // Report the build image's digest. 30 | output "image" { 31 | value = ko_build.app.image_ref 32 | } 33 | ``` 34 | 35 | See the [`ko-build/ko` provider on the Terraform Registry](https://registry.terraform.io/providers/ko-build/ko/latest) for more information, and the [GitHub repo](https://github.com/ko-build/terraform-provider-ko) for more examples. 36 | -------------------------------------------------------------------------------- /docs/community.md: -------------------------------------------------------------------------------- 1 | # Community 2 | 3 | ## Meetings 4 | 5 | We have a bi-weekly community meeting on [Wednesdays at 1:00 PM US Eastern time, 10:00 AM US Western time](https://dateful.com/eventlink/2763257725). The main goal of these meetings is that we want to hear from you! We want to know what you're using `ko` for, what you'd like to see in `ko`, how we can make `ko` better for you. With any remaining time we can go through open issues and PRs. 6 | 7 | We have a [meeting agenda](https://ko.build/agenda) you can use to propose topics for discussion/ideas. You can also just show up and we'll figure out what to talk about. 8 | 9 | ## Slack 10 | 11 | Come discuss `ko` with us in the `#ko-build` channel on the [Kubernetes Slack](https://ko.build/slack)! 12 | See you there! 13 | -------------------------------------------------------------------------------- /docs/custom/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block site_meta %} 4 | {{ super() }} 5 | {% if page and page.meta and page.meta.ko_meta %} 6 | 7 | 8 | 9 | {% endif %} 10 | {% endblock %} 11 | 12 | -------------------------------------------------------------------------------- /docs/custom/partials/copyright.html: -------------------------------------------------------------------------------- 1 | The Linux Foundation® (TLF) has registered trademarks and uses trademarks. For a list of TLF trademarks, see Trademark Usage 2 | -------------------------------------------------------------------------------- /docs/deployment.md: -------------------------------------------------------------------------------- 1 | # Deployment 2 | 3 | _See [Kubernetes Integration](../features/k8s) for information about deploying to Kubernetes._ 4 | 5 | Because the output of `ko build` is an image reference, you can easily pass it to other tools that expect to take an image reference. 6 | 7 | ### [`docker run`](https://docs.docker.com/engine/reference/run/) 8 | 9 | To run the container locally: 10 | 11 | ```plaintext 12 | docker run -p 8080:8080 $(ko build ./cmd/app) 13 | ``` 14 | 15 | --- 16 | 17 | ### [Google Cloud Run](https://cloud.google.com/run) 18 | 19 | ```plaintext 20 | gcloud run deploy --image=$(ko build ./cmd/app) 21 | ``` 22 | 23 | > 💡 **Note:** The image must be pushed to [Google Container Registry](https://cloud.google.com/container-registry) or [Artifact Registry](https://cloud.google.com/artifact-registry). 24 | 25 | --- 26 | 27 | ### [fly.io](https://fly.io) 28 | 29 | ```plaintext 30 | flyctl launch --image=$(ko build ./cmd/app) 31 | ``` 32 | 33 | > 💡 **Note:** The image must be pushed to Fly.io's container registry at `registry.fly.io`, or if not, the image must be publicly available. When pushing to `registry.fly.io`, you must first log in with [`flyctl auth docker`](https://fly.io/docs/flyctl/auth-docker/). 34 | 35 | --- 36 | 37 | ### [AWS Lambda](https://aws.amazon.com/lambda/) 38 | 39 | ```plaintext 40 | aws lambda update-function-code \ 41 | --function-name=my-function-name \ 42 | --image-uri=$(ko build ./cmd/app) 43 | ``` 44 | 45 | > 💡 **Note:** The image must be pushed to [ECR](https://aws.amazon.com/ecr/), based on the AWS provided base image, and use the [`aws-lambda-go`](https://github.com/aws/aws-lambda-go) framework. 46 | See [official docs](https://docs.aws.amazon.com/lambda/latest/dg/go-image.html) for more information. 47 | 48 | --- 49 | 50 | ### [Azure Container Apps](https://azure.microsoft.com/services/container-apps/) 51 | 52 | ```plaintext 53 | az containerapp update \ 54 | --name my-container-app 55 | --resource-group my-resource-group 56 | --image $(ko build ./cmd/app) 57 | ``` 58 | 59 | > 💡 **Note:** The image must be pushed to [ACR](https://azure.microsoft.com/services/container-registry/) or other registry service. 60 | See [official docs](https://docs.microsoft.com/azure/container-apps/) for more information. 61 | 62 | -------------------------------------------------------------------------------- /docs/features/build-cache.md: -------------------------------------------------------------------------------- 1 | # Build Cache 2 | 3 | Because `ko` just runs `go build` in your normal development environment, it automatically reuses your [`go build` cache](https://pkg.go.dev/cmd/go#hdr-Build_and_test_caching) from previous builds, making iterative development faster. 4 | 5 | `ko` also avoids pushing blobs to the remote image registry if they're already present, making pushes faster. 6 | 7 | You can make `ko` even faster by setting the `KOCACHE` environment variable. 8 | This tells `ko` to store a local mapping between the `go build` inputs to the image layer that they produce, so `go build` can be skipped entirely if the layer is already present in the image registry. 9 | 10 | -------------------------------------------------------------------------------- /docs/features/debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 | Sometimes it's challenging to track down the cause of unexpected behavior in an app. Because `ko` makes it simple to make tweaks to your app and immediately rebuild your image, it's possible to iteratively explore various aspects of your app, such as by adding log lines that print variable values. 4 | 5 | But to help you solve the problem _as fast as possible_, `ko` supports debugging your Go app with [delve](https://github.com/go-delve/delve). 6 | 7 | To use this feature, just add the `--debug` flag to your `ko build` command. This adjusts how the image is built: 8 | 9 | - It installs `delve` in the image (in addition to your own app). 10 | - It sets the image's `ENTRYPOINT` to a `delve exec ...` command that runs the Go app in debug-mode, listening on port `40000` for a debugger client. 11 | - It ensures your compiled Go app includes debug symbols needed to enable debugging. 12 | 13 | **Note:** This feature is geared toward development workflows. It **should not** be used in production. 14 | 15 | ### How it works 16 | 17 | Build the image using the debug feature. 18 | 19 | ```plaintext 20 | ko build . --debug 21 | ``` 22 | 23 | Run the container, ensuring that the debug port (`40000`) is exposed to allow clients to connect to it. 24 | 25 | ```plaintext 26 | docker run -p 40000:40000 27 | ``` 28 | 29 | This sets up your app to be waiting to run the command you've specified. All that's needed now is to connect your debugger client to the running container! 30 | -------------------------------------------------------------------------------- /docs/features/k8s.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Integration 2 | 3 | You _could_ stop at just building and pushing images. 4 | 5 | But, because building images is so _easy_ with `ko`, and because building with 6 | `ko` only requires a string importpath to identify the image, we can integrate 7 | this with YAML generation to make Kubernetes use cases much simpler. 8 | 9 | ## YAML Changes 10 | 11 | Traditionally, you might have a Kubernetes deployment, defined in a YAML file, 12 | that runs an image: 13 | 14 | ```yaml 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: my-deployment 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: my-app 23 | template: 24 | metadata: 25 | labels: 26 | app: my-app 27 | spec: 28 | containers: 29 | - name: my-app 30 | image: registry.example.com/my-app:v1.2.3 31 | ``` 32 | 33 | ...which you apply to your cluster with `kubectl apply`: 34 | 35 | ```plaintext 36 | kubectl apply -f deployment.yaml 37 | ``` 38 | 39 | With `ko`, you can instead reference your Go binary by its importpath, prefixed 40 | with `ko://`: 41 | 42 | ```yaml 43 | ... 44 | spec: 45 | containers: 46 | - name: my-app 47 | image: ko://github.com/my-user/my-repo/cmd/app 48 | ``` 49 | 50 | ## `ko resolve` 51 | 52 | With this small change, running `ko resolve -f deployment.yaml` will instruct 53 | `ko` to: 54 | 55 | 1. scan the YAML file(s) for values with the `ko://` prefix, 56 | 2. for each unique `ko://`-prefixed string, execute `ko build ` to 57 | build and push an image, 58 | 3. replace `ko://`-prefixed string(s) in the input YAML with the fully-specified 59 | image reference of the built image(s), as above. 60 | 4. Print the resulting resolved YAML to stdout. 61 | 62 | The result can be redirected to a file, to distribute to others: 63 | 64 | ```plaintext 65 | ko resolve -f config/ > release.yaml 66 | ``` 67 | 68 | Taken together, `ko resolve` aims to make packaging, pushing, and referencing 69 | container images an invisible implementation detail of your Kubernetes 70 | deployment, and let you focus on writing code in Go. 71 | 72 | ## `ko apply` 73 | 74 | To apply the resulting resolved YAML config, you can redirect the output of 75 | `ko resolve` to `kubectl apply`: 76 | 77 | ```plaintext 78 | ko resolve -f config/ | kubectl apply -f - 79 | ``` 80 | 81 | Since this is a relatively common use case, the same functionality is available 82 | using `ko apply`: 83 | 84 | ```plaintext 85 | ko apply -f config/ 86 | ``` 87 | 88 | Also, any flags passed after `--` are passed to `kubectl apply` directly, for example to specify context and kubeconfig: 89 | ``` 90 | ko apply -f config -- --context=foo --kubeconfig=cfg.yaml 91 | ``` 92 | 93 | **NB:** This requires that `kubectl` is available. 94 | 95 | ## `ko delete` 96 | 97 | To teardown resources applied using `ko apply`, you can run `ko delete`: 98 | 99 | ```plaintext 100 | ko delete -f config/ 101 | ``` 102 | 103 | This is purely a convenient alias for `kubectl delete`, and doesn't perform any 104 | builds, or delete any previously built images. 105 | 106 | -------------------------------------------------------------------------------- /docs/features/multi-platform.md: -------------------------------------------------------------------------------- 1 | # Multi-Platform Images 2 | 3 | Because Go supports cross-compilation to other CPU architectures and operating systems, `ko` excels at producing multi-platform images. 4 | 5 | To build and push an image for all platforms supported by the configured base image, simply add `--platform=all`. 6 | This will instruct `ko` to look up all the supported platforms in the base image, execute `GOOS= GOARCH= GOARM= go build` for each platform, and produce a manifest list containing an image for each platform. 7 | 8 | You can also select specific platforms, for example, `--platform=linux/amd64,linux/arm64`. 9 | 10 | `ko` also has experimental support for building for Windows images. 11 | See [FAQ](../../advanced/faq#can-i-build-windows-containers). 12 | 13 | -------------------------------------------------------------------------------- /docs/features/sboms.md: -------------------------------------------------------------------------------- 1 | # SBOMs 2 | 3 | A [Software Bill of Materials (SBOM)](https://en.wikipedia.org/wiki/Software_bill_of_materials) is a list of software components that a software artifact depends on. 4 | Having a list of dependencies can be helpful in determining whether any vulnerable components were used to build the software artifact. 5 | 6 | **From v0.9+, `ko` generates and uploads an SBOM for every image it produces by default.** 7 | 8 | ko will generate an SBOM in the [SPDX](https://spdx.dev/) format by default. To disable SBOM generation, pass `--sbom=none`. 9 | 10 | These SBOMs can be downloaded using the [`cosign download sbom`](https://github.com/sigstore/cosign/blob/main/doc/cosign_download_sbom.md) command. 11 | 12 | -------------------------------------------------------------------------------- /docs/features/static-assets.md: -------------------------------------------------------------------------------- 1 | # Static Assets 2 | 3 | `ko` can also bundle static assets into the images it produces. 4 | 5 | By convention, any contents of a directory named `/kodata/` will be 6 | bundled into the image, and the path where it's available in the image will be 7 | identified by the environment variable `KO_DATA_PATH`. 8 | 9 | As an example, you can bundle and serve static contents in your image: 10 | 11 | ``` 12 | cmd/ 13 | app/ 14 | main.go 15 | kodata/ 16 | favicon.ico 17 | index.html 18 | ``` 19 | 20 | Then, in your `main.go`: 21 | 22 | ```go 23 | func main() { 24 | http.Handle("/", http.FileServer(http.Dir(os.Getenv("KO_DATA_PATH")))) 25 | log.Fatal(http.ListenAndServe(":8080", nil)) 26 | } 27 | ``` 28 | 29 | You can simulate `ko`'s behavior outside of the container image by setting the 30 | `KO_DATA_PATH` environment variable yourself with `KO_DATA_PATH=cmd/app/kodata/ go run ./cmd/app`. 31 | 32 | > 💡 **Tip:** Symlinks in `kodata` are followed and included as well. For example, 33 | you can include Git commit information in your image with `ln -s -r .git/HEAD ./cmd/app/kodata/` 34 | 35 | Also note that `http.FileServer` will not serve the `Last-Modified` header 36 | (or validate `If-Modified-Since` request headers) because `ko` does not embed 37 | timestamps by default. 38 | 39 | This can be supported by manually setting the `KO_DATA_DATE_EPOCH` environment 40 | variable during build ([See FAQ](../../advanced/faq#why-are-my-images-all-created-in-1970)). 41 | 42 | -------------------------------------------------------------------------------- /docs/get-started.md: -------------------------------------------------------------------------------- 1 | # Get Started 2 | 3 | ## Setup 4 | 5 | First, [install `ko`](../install). 6 | 7 | ### Authenticate 8 | 9 | `ko` depends on the authentication configured in your Docker config (typically `~/.docker/config.json`). 10 | 11 | ✨ **If you can push an image with `docker push`, you are already authenticated for `ko`!** ✨ 12 | 13 | Since `ko` doesn't require `docker`, `ko login` also provides a surface for logging in to a container image registry with a username and password, similar to [`docker login`](https://docs.docker.com/engine/reference/commandline/login/). 14 | 15 | Additionally, even if auth is not configured in the Docker config, `ko` includes built-in support for authenticating to the following container registries using credentials configured in the environment: 16 | 17 | - Google Container Registry and Artifact Registry, using [Application Default Credentials](https://cloud.google.com/docs/authentication/production) or auth configured in `gcloud`. 18 | - Amazon Elastic Container Registry, using [AWS credentials](https://github.com/awslabs/amazon-ecr-credential-helper/#aws-credentials) 19 | - Azure Container Registry, using [environment variables](https://github.com/chrismellard/docker-credential-acr-env/) 20 | - GitHub Container Registry, using the `GITHUB_TOKEN` environment variable 21 | 22 | ### Choose Destination 23 | 24 | `ko` depends on an environment variable, `KO_DOCKER_REPO`, to identify where it should push images that it builds. Typically this will be a remote registry, e.g.: 25 | 26 | - `KO_DOCKER_REPO=gcr.io/my-project`, or 27 | - `KO_DOCKER_REPO=ghcr.io/my-org/my-repo`, or 28 | - `KO_DOCKER_REPO=my-dockerhub-user` 29 | 30 | ## Build an Image 31 | 32 | `ko build ./cmd/app` builds and pushes a container image, and prints the resulting image digest to stdout. 33 | 34 | In this example, `./cmd/app` must be a `package main` that defines `func main()`. 35 | 36 | ```plaintext 37 | $ ko build ./cmd/app 38 | ... 39 | registry.example.com/my-project/app-099ba5bcefdead87f92606265fb99ac0@sha256:6e398316742b7aa4a93161dce4a23bc5c545700b862b43347b941000b112ec3e 40 | ``` 41 | 42 | > 💡 **Note**: Prior to v0.10, the command was called `ko publish` -- this is equivalent to `ko build`, and both commands will work and do the same thing. 43 | 44 | The executable binary that was built from `./cmd/app` is available in the image at `/ko-app/app` -- the binary name matches the base import path name -- and that binary is the image's entrypoint. 45 | 46 | -------------------------------------------------------------------------------- /docs/images/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ko-build/ko/c47112d64ca597d8499920a0bbd2d37652143d2a/docs/images/android-icon-192x192.png -------------------------------------------------------------------------------- /docs/images/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ko-build/ko/c47112d64ca597d8499920a0bbd2d37652143d2a/docs/images/apple-icon-180x180.png -------------------------------------------------------------------------------- /docs/images/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ko-build/ko/c47112d64ca597d8499920a0bbd2d37652143d2a/docs/images/apple-icon.png -------------------------------------------------------------------------------- /docs/images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ko-build/ko/c47112d64ca597d8499920a0bbd2d37652143d2a/docs/images/demo.png -------------------------------------------------------------------------------- /docs/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ko-build/ko/c47112d64ca597d8499920a0bbd2d37652143d2a/docs/images/favicon-16x16.png -------------------------------------------------------------------------------- /docs/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ko-build/ko/c47112d64ca597d8499920a0bbd2d37652143d2a/docs/images/favicon-32x32.png -------------------------------------------------------------------------------- /docs/images/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ko-build/ko/c47112d64ca597d8499920a0bbd2d37652143d2a/docs/images/favicon-96x96.png -------------------------------------------------------------------------------- /docs/images/ko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ko-build/ko/c47112d64ca597d8499920a0bbd2d37652143d2a/docs/images/ko.png -------------------------------------------------------------------------------- /docs/images/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ko-build/ko/c47112d64ca597d8499920a0bbd2d37652143d2a/docs/images/ms-icon-144x144.png -------------------------------------------------------------------------------- /docs/images/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ko-build/ko/c47112d64ca597d8499920a0bbd2d37652143d2a/docs/images/og.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | ko_meta: true 3 | --- 4 | 5 | # Introduction 6 | 7 | `ko` makes building Go container images easy, fast, and secure by default. 8 | 9 | ![Demo of ko build](./images/demo.png) 10 | 11 | `ko` is a simple, fast container image builder for Go applications. 12 | 13 | It's ideal for use cases where your image contains a single Go application without many dependencies on the OS base image (e.g., no cgo, no OS package dependencies). 14 | 15 | `ko` builds images by executing `go build` on your local machine, and as such doesn't require `docker` to be installed. 16 | This can make it a good fit for lightweight CI/CD use cases. 17 | 18 | `ko` makes [multi-platform builds](https://ko.build/features/multi-platform/) easy, produces [SBOMs](https://ko.build/features/sboms/) by default, and includes support for simple YAML templating which makes it a powerful tool for [Kubernetes applications](https://ko.build/features/k8s/). 19 | 20 | --- 21 | 22 | > 🏃 [Install `ko`](./install) and [get started](./get-started)! 23 | 24 | --- 25 | 26 | `ko` is used and loved by these open source projects: 27 | 28 | - [Knative](https://knative.dev) 29 | - [Tekton](https://tekton.dev) 30 | - [Karpenter](https://karpenter.sh) 31 | - [Kyverno](https://kyverno.io) 32 | - [Sigstore](https://sigstore.dev) 33 | - [Shipwright](https://shipwright.io) 34 | - [Capsule](https://capsule.clastix.io/) 35 | - [CloudScript](https://cloudscript.com.br/) 36 | - [Kamaji](https://kamaji.clastix.io/) 37 | 38 | [_Add your project here!_](https://github.com/ko-build/ko/edit/main/docs/index.md) 39 | 40 | --- 41 | 42 | `ko` is a Cloud Native Computing Foundation Sandbox project. 43 | 44 | 45 | CNCF logo 46 | CNCF logo 47 | 48 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ### Install from [GitHub Releases](https://github.com/ko-build/ko/releases) 4 | 5 | ``` 6 | $ VERSION=TODO # choose the latest version (without v prefix) 7 | $ OS=Linux # or Darwin 8 | $ ARCH=x86_64 # or arm64, i386, s390x 9 | ``` 10 | 11 | We generate [SLSA3 provenance](https://slsa.dev) using the OpenSSF's [slsa-framework/slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator). To verify our release, install the verification tool from [slsa-framework/slsa-verifier#installation](https://github.com/slsa-framework/slsa-verifier#installation) and verify as follows: 12 | 13 | 14 | ```shell 15 | $ curl -sSfL "https://github.com/ko-build/ko/releases/download/v${VERSION}/ko_${VERSION}_${OS}_${ARCH}.tar.gz" > ko.tar.gz 16 | $ curl -sSfL https://github.com/ko-build/ko/releases/download/v${VERSION}/multiple.intoto.jsonl > multiple.intoto.jsonl 17 | $ slsa-verifier verify-artifact --provenance-path multiple.intoto.jsonl --source-uri github.com/ko-build/ko --source-tag "v${VERSION}" ko.tar.gz 18 | Verified signature against tlog entry index 24413745 at URL: https://rekor.sigstore.dev/api/v1/log/entries/24296fb24b8ad77ab97a5263b5fa8f35789618348a39358b1f9470b0c31045effbbe5e23e77a5836 19 | Verified build using builder "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v1.7.0" at commit 200db7243f02b5c0303e21d8ab8e3b4ad3a229d0 20 | Verifying artifact /Users/batuhanapaydin/workspace/ko/ko.tar.gz: PASSED 21 | 22 | PASSED: Verified SLSA provenance 23 | ``` 24 | 25 | ```shell 26 | $ tar xzf ko.tar.gz ko 27 | $ chmod +x ./ko 28 | ``` 29 | 30 | ### Install using [Homebrew](https://brew.sh) 31 | 32 | ```plaintext 33 | brew install ko 34 | ``` 35 | 36 | ### Install using [MacPorts](https://www.macports.org) 37 | 38 | ```plaintext 39 | sudo port install ko 40 | ``` 41 | 42 | More info [here](https://ports.macports.org/port/ko/) 43 | 44 | ### Install on Windows using [Scoop](https://scoop.sh) 45 | 46 | ```plaintext 47 | scoop install ko 48 | ``` 49 | 50 | ### Install on [Alpine Linux](https://www.alpinelinux.org) 51 | 52 | Installation on Alpine requires using the [`testing` repository](https://wiki.alpinelinux.org/wiki/Enable_Community_Repository#Using_testing_repositories) 53 | 54 | ``` 55 | echo https://dl-cdn.alpinelinux.org/alpine/edge/testing/ >> /etc/apk/repositories 56 | apk update 57 | apk add ko 58 | ``` 59 | 60 | ### Build and Install from source 61 | 62 | With Go 1.16+, build and install the latest released version: 63 | 64 | ```plaintext 65 | go install github.com/google/ko@latest 66 | ``` 67 | 68 | ### Setup on GitHub Actions 69 | 70 | You can use the [setup-ko](https://github.com/ko-build/setup-ko) action to install ko and setup auth to [GitHub Container Registry](https://github.com/features/packages) in a GitHub Action workflow: 71 | 72 | ```plaintext 73 | steps: 74 | - uses: ko-build/setup-ko@v0.6 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/reference/ko.md: -------------------------------------------------------------------------------- 1 | ## ko 2 | 3 | Rapidly iterate with Go, Containers, and Kubernetes. 4 | 5 | ``` 6 | ko [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for ko 13 | -v, --verbose Enable debug logs 14 | ``` 15 | 16 | ### SEE ALSO 17 | 18 | * [ko apply](ko_apply.md) - Apply the input files with image references resolved to built/pushed image digests. 19 | * [ko build](ko_build.md) - Build and publish container images from the given importpaths. 20 | * [ko create](ko_create.md) - Create the input files with image references resolved to built/pushed image digests. 21 | * [ko delete](ko_delete.md) - See "kubectl help delete" for detailed usage. 22 | * [ko login](ko_login.md) - Log in to a registry 23 | * [ko resolve](ko_resolve.md) - Print the input files with image references resolved to built/pushed image digests. 24 | * [ko run](ko_run.md) - A variant of `kubectl run` that containerizes IMPORTPATH first. 25 | * [ko version](ko_version.md) - Print ko version. 26 | 27 | -------------------------------------------------------------------------------- /docs/reference/ko_delete.md: -------------------------------------------------------------------------------- 1 | ## ko delete 2 | 3 | See "kubectl help delete" for detailed usage. 4 | 5 | ``` 6 | ko delete [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for delete 13 | ``` 14 | 15 | ### Options inherited from parent commands 16 | 17 | ``` 18 | -v, --verbose Enable debug logs 19 | ``` 20 | 21 | ### SEE ALSO 22 | 23 | * [ko](ko.md) - Rapidly iterate with Go, Containers, and Kubernetes. 24 | 25 | -------------------------------------------------------------------------------- /docs/reference/ko_login.md: -------------------------------------------------------------------------------- 1 | ## ko login 2 | 3 | Log in to a registry 4 | 5 | ``` 6 | ko login [OPTIONS] [SERVER] [flags] 7 | ``` 8 | 9 | ### Examples 10 | 11 | ``` 12 | # Log in to reg.example.com 13 | ko login reg.example.com -u AzureDiamond -p hunter2 14 | ``` 15 | 16 | ### Options 17 | 18 | ``` 19 | -h, --help help for login 20 | -p, --password string Password 21 | --password-stdin Take the password from stdin 22 | -u, --username string Username 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -v, --verbose Enable debug logs 29 | ``` 30 | 31 | ### SEE ALSO 32 | 33 | * [ko](ko.md) - Rapidly iterate with Go, Containers, and Kubernetes. 34 | 35 | -------------------------------------------------------------------------------- /docs/reference/ko_run.md: -------------------------------------------------------------------------------- 1 | ## ko run 2 | 3 | A variant of `kubectl run` that containerizes IMPORTPATH first. 4 | 5 | ### Synopsis 6 | 7 | This sub-command combines "ko build" and "kubectl run" to support containerizing and running Go binaries on Kubernetes in a single command. 8 | 9 | ``` 10 | ko run IMPORTPATH [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | 17 | # Publish the image and run it on Kubernetes as: 18 | # ${KO_DOCKER_REPO}/- 19 | # When KO_DOCKER_REPO is ko.local, it is the same as if 20 | # --local and --preserve-import-paths were passed. 21 | ko run github.com/foo/bar/cmd/baz 22 | 23 | # This supports relative import paths as well. 24 | ko run ./cmd/baz 25 | 26 | # You can also supply args and flags to the command. 27 | ko run ./cmd/baz -- -v arg1 arg2 --yes 28 | ``` 29 | 30 | ### Options 31 | 32 | ``` 33 | --bare Whether to just use KO_DOCKER_REPO without additional context (may not work properly with --tags). 34 | -B, --base-import-paths Whether to use the base path without MD5 hash after KO_DOCKER_REPO (may not work properly with --tags). 35 | --debug Include Delve debugger into image and wrap around ko-app. This debugger will listen to port 40000. 36 | --disable-optimizations Disable optimizations when building Go code. Useful when you want to interactively debug the created container. 37 | -h, --help help for run 38 | --image-annotation strings Which annotations (key=value[,key=value]) to add to the OCI manifest. 39 | --image-label strings Which labels (key=value[,key=value]) to add to the image. 40 | --image-refs string Path to file where a list of the published image references will be written. 41 | --image-user string The default user the image should be run as. 42 | --insecure-registry Whether to skip TLS verification on the registry 43 | -j, --jobs int The maximum number of concurrent builds (default GOMAXPROCS) 44 | -L, --local Load into images to local docker daemon. 45 | --oci-layout-path string Path to save the OCI image layout of the built images 46 | --platform strings Which platform to use when pulling a multi-platform base. Format: all | [/[/]][,platform]* 47 | -P, --preserve-import-paths Whether to preserve the full import path after KO_DOCKER_REPO. 48 | --push Push images to KO_DOCKER_REPO (default true) 49 | --sbom string The SBOM media type to use (none will disable SBOM synthesis and upload). (default "spdx") 50 | --sbom-dir string Path to file where the SBOM will be written. 51 | --tag-only Include tags but not digests in resolved image references. Useful when digests are not preserved when images are repopulated. 52 | -t, --tags strings Which tags to use for the produced image instead of the default 'latest' tag (may not work properly with --base-import-paths or --bare). (default [latest]) 53 | --tarball string File to save images tarballs 54 | ``` 55 | 56 | ### Options inherited from parent commands 57 | 58 | ``` 59 | -v, --verbose Enable debug logs 60 | ``` 61 | 62 | ### SEE ALSO 63 | 64 | * [ko](ko.md) - Rapidly iterate with Go, Containers, and Kubernetes. 65 | 66 | -------------------------------------------------------------------------------- /docs/reference/ko_version.md: -------------------------------------------------------------------------------- 1 | ## ko version 2 | 3 | Print ko version. 4 | 5 | ``` 6 | ko version [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for version 13 | ``` 14 | 15 | ### Options inherited from parent commands 16 | 17 | ``` 18 | -v, --verbose Enable debug logs 19 | ``` 20 | 21 | ### SEE ALSO 22 | 23 | * [ko](ko.md) - Rapidly iterate with Go, Containers, and Kubernetes. 24 | 25 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2023 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.sh.txt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2023 ko Build Authors All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | -------------------------------------------------------------------------------- /hack/presubmit.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2021 ko Build Authors All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" 22 | 23 | pushd "${PROJECT_ROOT}" 24 | trap popd EXIT 25 | 26 | # Verify that all source files are correctly formatted. 27 | find . -name "*.go" -exec gofmt -d -e -l {} + 28 | 29 | # Verify that generated Markdown docs are up-to-date. 30 | tmpdir=$(mktemp -d) 31 | go run cmd/help/main.go --dir "$tmpdir" 32 | diff -Naur -I '###### Auto generated' "$tmpdir" docs/reference/ 33 | -------------------------------------------------------------------------------- /hack/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build tools 16 | // +build tools 17 | 18 | package hack 19 | 20 | import ( 21 | _ "github.com/go-training/helloworld" 22 | ) 23 | -------------------------------------------------------------------------------- /hack/update-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2021 ko Build Authors All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" 22 | 23 | pushd ${PROJECT_ROOT} 24 | trap popd EXIT 25 | 26 | go mod tidy 27 | 28 | go run $PROJECT_ROOT/cmd/help/main.go --dir=$PROJECT_ROOT/docs/reference/ 29 | -------------------------------------------------------------------------------- /hack/update-deps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2018 ko Build Authors All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" 22 | 23 | pushd ${PROJECT_ROOT} 24 | trap popd EXIT 25 | 26 | go mod tidy 27 | -------------------------------------------------------------------------------- /integration_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit 3 | set -o nounset 4 | set -o pipefail 5 | 6 | ROOT_DIR=$(dirname "$0") 7 | 8 | pushd "$ROOT_DIR" 9 | 10 | ROOT_DIR="$(pwd)" 11 | 12 | echo "Moving GOPATH into /tmp/ to test modules behavior." 13 | export GOPATH="${GOPATH:-$(go env GOPATH)}" 14 | export ORIGINAL_GOPATH="$GOPATH" 15 | GOPATH="$(mktemp -d)" 16 | export GOPATH 17 | 18 | export CGO_ENABLED=0 19 | 20 | GOARCH="${GOARCH:-$(go env GOARCH)}" 21 | 22 | pushd "$GOPATH" || exit 1 23 | 24 | echo "Copying ko to temp gopath." 25 | mkdir -p "$GOPATH/src/github.com/google/ko" 26 | cp -r "$ROOT_DIR/"* "$GOPATH/src/github.com/google/ko/" 27 | 28 | pushd "$GOPATH/src/github.com/google/ko" || exit 1 29 | 30 | echo "Building ko" 31 | 32 | RESULT="$(go build .)" 33 | 34 | echo "Beginning scenarios." 35 | 36 | FILTER="[^ ]local[^ ]*" 37 | 38 | echo "1. Test should create an image that outputs 'Hello World'." 39 | RESULT="$(./ko build --local --platform="linux/$GOARCH" "$GOPATH/src/github.com/google/ko/test" | grep "$FILTER" | xargs -I% docker run %)" 40 | if [[ "$RESULT" != *"Hello there"* ]]; then 41 | echo "Test FAILED. Saw $RESULT" && exit 1 42 | else 43 | echo "Test PASSED" 44 | fi 45 | 46 | echo "2. Test knative 'KO_FLAGS' variable is ignored." 47 | # https://github.com/ko-build/ko/issues/1317 48 | RESULT="$(KO_FLAGS="--platform=badvalue" ./ko build --local --platform="linux/$GOARCH" "$GOPATH/src/github.com/google/ko/test" | grep "$FILTER" | xargs -I% docker run %)" 49 | if [[ "$RESULT" != *"Hello there"* ]]; then 50 | echo "Test FAILED. Saw $RESULT" && exit 1 51 | else 52 | echo "Test PASSED" 53 | fi 54 | 55 | echo "3. Linux capabilities." 56 | pushd test/build-configs || exit 1 57 | # run as non-root user with net_bind_service cap granted 58 | docker_run_opts="--user 1 --cap-add=net_bind_service" 59 | RESULT="$(../../ko build --local --platform="linux/$GOARCH" ./caps/cmd | grep "$FILTER" | xargs -I% docker run $docker_run_opts %)" 60 | if [[ "$RESULT" != "No capabilities" ]]; then 61 | echo "Test FAILED. Saw '$RESULT' but expected 'No capabilities'. Docker 'cap-add' must have no effect unless matching capabilities are granted to the file." && exit 1 62 | fi 63 | # build with a different config requesting net_bind_service file capability 64 | RESULT_WITH_FILE_CAPS="$(KO_CONFIG_PATH=caps.ko.yaml ../../ko build --local --platform="linux/$GOARCH" ./caps/cmd | grep "$FILTER" | xargs -I% docker run $docker_run_opts %)" 65 | if [[ "$RESULT_WITH_FILE_CAPS" != "Has capabilities"* ]]; then 66 | echo "Test FAILED. Saw '$RESULT_WITH_FILE_CAPS' but expected 'Has capabilities'. Docker 'cap-add' must work when matching capabilities are granted to the file." && exit 1 67 | else 68 | echo "Test PASSED" 69 | fi 70 | popd || exit 1 71 | 72 | popd || exit 1 73 | popd || exit 1 74 | 75 | export GOPATH="$ORIGINAL_GOPATH" 76 | -------------------------------------------------------------------------------- /internal/sbom/sbom.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sbom 16 | 17 | import ( 18 | "bufio" 19 | "bytes" 20 | "fmt" 21 | "runtime/debug" 22 | "strings" 23 | "unicode" 24 | ) 25 | 26 | func modulePackageName(mod *debug.Module) string { 27 | return fmt.Sprintf("SPDXRef-Package-%s-%s", 28 | strings.ReplaceAll(mod.Path, "/", "."), 29 | mod.Version) 30 | } 31 | 32 | func goRef(mod *debug.Module) string { 33 | path := mod.Path 34 | // Try to lowercase the first 2 path elements to comply with spec 35 | // https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#golang 36 | p := strings.Split(path, "/") 37 | if len(p) > 2 { 38 | path = strings.Join( 39 | append( 40 | []string{strings.ToLower(p[0]), strings.ToLower(p[1])}, 41 | p[2:]..., 42 | ), "/", 43 | ) 44 | } 45 | return fmt.Sprintf("pkg:golang/%s@%s?type=module", path, mod.Version) 46 | } 47 | 48 | // massageGoVersionM massages the output of `go version -m` into a form that 49 | // can be consumed by ParseBuildInfo. 50 | // 51 | // `go version -m` adds a line at the beginning of its output, and tabs at the 52 | // beginning of every line, that ParseBuildInfo doesn't like. 53 | func massageGoVersionM(b []byte) ([]byte, error) { 54 | var out bytes.Buffer 55 | scanner := bufio.NewScanner(bytes.NewReader(b)) 56 | if !scanner.Scan() { 57 | // Input was malformed, and doesn't contain any newlines (it 58 | // may even be empty). This seems to happen on Windows 59 | // (https://github.com/ko-build/ko/issues/535) and in unit tests. 60 | // Just proceed with an empty output for now, and SBOMs will be empty. 61 | // TODO: This should be an error. 62 | return nil, nil 63 | } 64 | if err := scanner.Err(); err != nil { 65 | return nil, fmt.Errorf("malformed input: %w", err) 66 | } 67 | for scanner.Scan() { 68 | // NOTE: debug.ParseBuildInfo relies on trailing tabs. 69 | line := strings.TrimLeftFunc(scanner.Text(), unicode.IsSpace) 70 | fmt.Fprintln(&out, line) 71 | } 72 | if err := scanner.Err(); err != nil { 73 | return nil, err 74 | } 75 | return out.Bytes(), nil 76 | } 77 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:generate go run ./cmd/help/main.go -d docs/reference/ 16 | package main 17 | 18 | import ( 19 | "context" 20 | "os" 21 | "os/signal" 22 | 23 | "github.com/google/ko/pkg/commands" 24 | ) 25 | 26 | func main() { 27 | ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) 28 | defer stop() 29 | if err := commands.Root.ExecuteContext(ctx); err != nil { 30 | os.Exit(1) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: 'ko: Easy Go Containers' 2 | site_url: https://ko.build 3 | repo_url: https://github.com/ko-build/ko 4 | edit_uri: edit/main/docs/ 5 | 6 | theme: 7 | name: material 8 | logo: images/favicon-96x96.png 9 | favicon: images/favicon-96x96.png 10 | custom_dir: docs/custom/ 11 | palette: 12 | # Palette toggle for automatic mode 13 | - media: "(prefers-color-scheme)" 14 | toggle: 15 | icon: material/brightness-auto 16 | name: Switch to light mode 17 | 18 | # Palette toggle for light mode 19 | - media: "(prefers-color-scheme: light)" 20 | primary: light blue 21 | toggle: 22 | icon: material/brightness-7 23 | name: Switch to dark mode 24 | 25 | # Palette toggle for dark mode 26 | - media: "(prefers-color-scheme: dark)" 27 | scheme: slate 28 | primary: light blue 29 | toggle: 30 | icon: material/brightness-4 31 | name: Switch to system preference 32 | 33 | nav: 34 | - index.md 35 | - install.md 36 | - get-started.md 37 | - configuration.md 38 | - deployment.md 39 | - community.md 40 | - Features: 41 | - features/multi-platform.md 42 | - features/sboms.md 43 | - features/k8s.md 44 | - features/static-assets.md 45 | - features/build-cache.md 46 | - features/debugging.md 47 | - Advanced: 48 | - advanced/go-packages.md 49 | - advanced/limitations.md 50 | - advanced/migrating-from-dockerfile.md 51 | - advanced/faq.md 52 | - advanced/terraform.md 53 | - advanced/lambda.md 54 | - advanced/linux-capabilities.md 55 | - advanced/root-ca-certificates.md 56 | - CLI Reference: 57 | - 'ko': reference/ko.md 58 | - 'ko apply': reference/ko_apply.md 59 | - 'ko build': reference/ko_build.md 60 | - 'ko create': reference/ko_create.md 61 | - 'ko delete': reference/ko_delete.md 62 | - 'ko login': reference/ko_login.md 63 | - 'ko resolve': reference/ko_resolve.md 64 | - 'ko run': reference/ko_run.md 65 | - 'ko version': reference/ko_version.md 66 | - Releases: "https://github.com/ko-build/ko/releases" 67 | 68 | plugins: 69 | - search 70 | - redirects: 71 | redirect_maps: 72 | 'repo.md': 'https://github.com/ko-build/ko' 73 | 'issues.md': 'https://github.com/ko-build/ko/issues' 74 | 'prs.md': 'https://github.com/ko-build/ko/pulls' 75 | 'releases.md': 'https://github.com/ko-build/ko/releases' 76 | 'godoc.md': 'https://pkg.go.dev/github.com/google/ko' 77 | 'terraform.md': 'https://github.com/ko-build/terraform-provider-ko' 78 | 'action.md': 'https://github.com/ko-build/setup-ko' 79 | 'slack.md': 'https://kubernetes.slack.com/archives/C01T7DTP65S' 80 | 'agenda.md': 'https://docs.google.com/document/d/1eQ67Qxwf1tkTv0yU_dw9bIRnlwJZz-5GXCRVOhqbgvU/edit' 81 | 'meet.md': 'meet.google.com/xvn-dzzk-wur' 82 | 83 | markdown_extensions: 84 | - pymdownx.highlight: 85 | anchor_linenums: true 86 | line_spans: __span 87 | pygments_lang_class: true 88 | - pymdownx.inlinehilite 89 | - pymdownx.snippets 90 | - pymdownx.superfences 91 | -------------------------------------------------------------------------------- /pkg/build/build.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package build 16 | 17 | import ( 18 | "context" 19 | 20 | v1 "github.com/google/go-containerregistry/pkg/v1" 21 | "github.com/google/go-containerregistry/pkg/v1/types" 22 | ) 23 | 24 | // Interface abstracts different methods for turning a supported importpath 25 | // reference into a v1.Image. 26 | type Interface interface { 27 | // QualifyImport turns relative importpath references into complete importpaths. 28 | // It also adds the ko scheme prefix if necessary. 29 | // E.g., "github.com/ko-build/ko/test" => "ko://github.com/ko-build/ko/test" 30 | // and "./test" => "ko://github.com/ko-build/ko/test" 31 | QualifyImport(string) (string, error) 32 | 33 | // IsSupportedReference determines whether the given reference is to an 34 | // importpath reference that Ko supports building, returning an error 35 | // if it is not. 36 | // TODO(mattmoor): Verify that some base repo: foo.io/bar can be suffixed with this reference and parsed. 37 | IsSupportedReference(string) error 38 | 39 | // Build turns the given importpath reference into a v1.Image containing the Go binary 40 | // (or a set of images as a v1.ImageIndex). 41 | Build(context.Context, string) (Result, error) 42 | } 43 | 44 | // Result represents the product of a Build. 45 | // This is generally one of: 46 | // - v1.Image (or oci.SignedImage), or 47 | // - v1.ImageIndex (or oci.SignedImageIndex) 48 | type Result interface { 49 | MediaType() (types.MediaType, error) 50 | Size() (int64, error) 51 | Digest() (v1.Hash, error) 52 | RawManifest() ([]byte, error) 53 | } 54 | 55 | // Assert that Image and ImageIndex implement Result. 56 | var _ Result = (v1.Image)(nil) 57 | var _ Result = (v1.ImageIndex)(nil) 58 | -------------------------------------------------------------------------------- /pkg/build/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package build defines methods for building a v1.Image reference from a 16 | // Go binary reference. 17 | package build 18 | -------------------------------------------------------------------------------- /pkg/build/future.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package build 16 | 17 | import ( 18 | "sync" 19 | ) 20 | 21 | func newFuture(work func() (Result, error)) *future { 22 | // Create a channel on which to send the result. 23 | ch := make(chan *result) 24 | // Initiate the actual work, sending its result 25 | // along the above channel. 26 | go func() { 27 | img, err := work() 28 | ch <- &result{img: img, err: err} 29 | }() 30 | // Return a future for the above work. Callers should 31 | // call .Get() on this result (as many times as needed). 32 | // One of these calls will receive the result, store it, 33 | // and close the channel so that the rest of the callers 34 | // can consume it. 35 | return &future{ 36 | promise: ch, 37 | } 38 | } 39 | 40 | type result struct { 41 | img Result 42 | err error 43 | } 44 | 45 | type future struct { 46 | m sync.RWMutex 47 | 48 | result *result 49 | promise chan *result 50 | } 51 | 52 | // Get blocks on the result of the future. 53 | func (f *future) Get() (Result, error) { 54 | // Block on the promise of a result until we get one. 55 | result, ok := <-f.promise 56 | if ok { 57 | func() { 58 | f.m.Lock() 59 | defer f.m.Unlock() 60 | // If we got the result, then store it so that 61 | // others may access it. 62 | f.result = result 63 | // Close the promise channel so that others 64 | // are signaled that the result is available. 65 | close(f.promise) 66 | }() 67 | } 68 | 69 | f.m.RLock() 70 | defer f.m.RUnlock() 71 | 72 | return f.result.img, f.result.err 73 | } 74 | -------------------------------------------------------------------------------- /pkg/build/future_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package build 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/google/go-containerregistry/pkg/v1/random" 21 | ) 22 | 23 | func makeImage() (Result, error) { 24 | return random.Index(256, 8, 1) 25 | } 26 | 27 | func digest(t *testing.T, img Result) string { 28 | d, err := img.Digest() 29 | if err != nil { 30 | t.Fatalf("Digest() = %v", err) 31 | } 32 | return d.String() 33 | } 34 | 35 | func TestSameFutureSameImage(t *testing.T) { 36 | f := newFuture(makeImage) 37 | 38 | i1, err := f.Get() 39 | if err != nil { 40 | t.Errorf("Get() = %v", err) 41 | } 42 | d1 := digest(t, i1) 43 | 44 | i2, err := f.Get() 45 | if err != nil { 46 | t.Errorf("Get() = %v", err) 47 | } 48 | d2 := digest(t, i2) 49 | 50 | if d1 != d2 { 51 | t.Errorf("Got different digests %s and %s", d1, d2) 52 | } 53 | } 54 | 55 | func TestDiffFutureDiffImage(t *testing.T) { 56 | f1 := newFuture(makeImage) 57 | f2 := newFuture(makeImage) 58 | 59 | i1, err := f1.Get() 60 | if err != nil { 61 | t.Errorf("Get() = %v", err) 62 | } 63 | d1 := digest(t, i1) 64 | 65 | i2, err := f2.Get() 66 | if err != nil { 67 | t.Errorf("Get() = %v", err) 68 | } 69 | d2 := digest(t, i2) 70 | 71 | if d1 == d2 { 72 | t.Errorf("Got same digest %s, wanted different", d1) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /pkg/build/layer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package build 16 | 17 | import ( 18 | "io" 19 | "sync" 20 | 21 | v1 "github.com/google/go-containerregistry/pkg/v1" 22 | "github.com/google/go-containerregistry/pkg/v1/types" 23 | ) 24 | 25 | type lazyLayer struct { 26 | diffid v1.Hash 27 | desc v1.Descriptor 28 | 29 | sync.Once 30 | buildLayer func() (v1.Layer, error) 31 | layer v1.Layer 32 | err error 33 | } 34 | 35 | // All this info is cached by previous builds. 36 | func (l *lazyLayer) Digest() (v1.Hash, error) { 37 | return l.desc.Digest, nil 38 | } 39 | 40 | func (l *lazyLayer) DiffID() (v1.Hash, error) { 41 | return l.diffid, nil 42 | } 43 | 44 | func (l *lazyLayer) Size() (int64, error) { 45 | return l.desc.Size, nil 46 | } 47 | 48 | func (l *lazyLayer) MediaType() (types.MediaType, error) { 49 | return l.desc.MediaType, nil 50 | } 51 | 52 | // This is only called if the registry doesn't have this blob already. 53 | func (l *lazyLayer) Compressed() (io.ReadCloser, error) { 54 | layer, err := l.compute() 55 | if err != nil { 56 | return nil, err 57 | } 58 | return layer.Compressed() 59 | } 60 | 61 | // This should never actually be called but we need it to impl v1.Layer. 62 | func (l *lazyLayer) Uncompressed() (io.ReadCloser, error) { 63 | layer, err := l.compute() 64 | if err != nil { 65 | return nil, err 66 | } 67 | return layer.Uncompressed() 68 | } 69 | 70 | func (l *lazyLayer) compute() (v1.Layer, error) { 71 | l.Do(func() { 72 | l.layer, l.err = l.buildLayer() 73 | }) 74 | return l.layer, l.err 75 | } 76 | -------------------------------------------------------------------------------- /pkg/build/limit.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package build 16 | 17 | import ( 18 | "context" 19 | 20 | "golang.org/x/sync/semaphore" 21 | ) 22 | 23 | // Limiter composes with another Interface to limit the number of concurrent builds. 24 | type Limiter struct { 25 | Builder Interface 26 | semaphore *semaphore.Weighted 27 | } 28 | 29 | // Limiter implements Interface 30 | var _ Interface = (*Recorder)(nil) 31 | 32 | // QualifyImport implements Interface 33 | func (l *Limiter) QualifyImport(ip string) (string, error) { 34 | return l.Builder.QualifyImport(ip) 35 | } 36 | 37 | // IsSupportedReference implements Interface 38 | func (l *Limiter) IsSupportedReference(ip string) error { 39 | return l.Builder.IsSupportedReference(ip) 40 | } 41 | 42 | // Build implements Interface 43 | func (l *Limiter) Build(ctx context.Context, ip string) (Result, error) { 44 | if err := l.semaphore.Acquire(ctx, 1); err != nil { 45 | return nil, err 46 | } 47 | defer l.semaphore.Release(1) 48 | 49 | return l.Builder.Build(ctx, ip) 50 | } 51 | 52 | // NewLimiter returns a new builder that only allows n concurrent builds of b. 53 | // 54 | // Deprecated: Obsoleted by WithJobs option. 55 | func NewLimiter(b Interface, n int) *Limiter { 56 | return &Limiter{ 57 | Builder: b, 58 | semaphore: semaphore.NewWeighted(int64(n)), 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/build/limit_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package build 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | "time" 21 | 22 | "golang.org/x/sync/errgroup" 23 | ) 24 | 25 | type sleeper struct{} 26 | 27 | var _ Interface = (*sleeper)(nil) 28 | 29 | // QualifyImport implements Interface 30 | func (*sleeper) QualifyImport(ip string) (string, error) { 31 | return ip, nil 32 | } 33 | 34 | // IsSupportedReference implements Interface 35 | func (*sleeper) IsSupportedReference(_ string) error { 36 | return nil 37 | } 38 | 39 | // Build implements Interface 40 | func (*sleeper) Build(_ context.Context, _ string) (Result, error) { 41 | time.Sleep(50 * time.Millisecond) 42 | return nil, nil 43 | } 44 | 45 | func TestLimiter(t *testing.T) { 46 | b := NewLimiter(&sleeper{}, 2) 47 | 48 | start := time.Now() 49 | g, _ := errgroup.WithContext(context.TODO()) 50 | for i := 0; i <= 10; i++ { 51 | g.Go(func() error { 52 | _, _ = b.Build(context.Background(), "whatever") 53 | return nil 54 | }) 55 | } 56 | g.Wait() 57 | 58 | // 50 ms * 10 builds / 2 concurrency = ~250ms 59 | if time.Now().Before(start.Add(250 * time.Millisecond)) { 60 | t.Fatal("Too many builds") 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pkg/build/recorder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package build 16 | 17 | import ( 18 | "context" 19 | "sync" 20 | ) 21 | 22 | // Recorder composes with another Interface to record the built import paths. 23 | type Recorder struct { 24 | m sync.Mutex 25 | ImportPaths []string 26 | Builder Interface 27 | } 28 | 29 | // Recorder implements Interface 30 | var _ Interface = (*Recorder)(nil) 31 | 32 | // QualifyImport implements Interface 33 | func (r *Recorder) QualifyImport(ip string) (string, error) { 34 | return r.Builder.QualifyImport(ip) 35 | } 36 | 37 | // IsSupportedReference implements Interface 38 | func (r *Recorder) IsSupportedReference(ip string) error { 39 | return r.Builder.IsSupportedReference(ip) 40 | } 41 | 42 | // Build implements Interface 43 | func (r *Recorder) Build(ctx context.Context, ip string) (Result, error) { 44 | func() { 45 | r.m.Lock() 46 | defer r.m.Unlock() 47 | r.ImportPaths = append(r.ImportPaths, ip) 48 | }() 49 | return r.Builder.Build(ctx, ip) 50 | } 51 | -------------------------------------------------------------------------------- /pkg/build/recorder_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package build 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | 21 | "github.com/google/go-cmp/cmp" 22 | ) 23 | 24 | type fake struct { 25 | isr func(string) error 26 | b func(string) (Result, error) 27 | } 28 | 29 | var _ Interface = (*fake)(nil) 30 | 31 | // QualifyImport implements Interface 32 | func (r *fake) QualifyImport(ip string) (string, error) { return ip, nil } 33 | 34 | // IsSupportedReference implements Interface 35 | func (r *fake) IsSupportedReference(ip string) error { return r.isr(ip) } 36 | 37 | // Build implements Interface 38 | func (r *fake) Build(_ context.Context, ip string) (Result, error) { return r.b(ip) } 39 | 40 | func TestISRPassThrough(t *testing.T) { 41 | tests := []struct { 42 | name string 43 | input string 44 | }{{ 45 | name: "empty string", 46 | }, { 47 | name: "non-empty string", 48 | input: "asdf asdf asdf", 49 | }} 50 | 51 | for _, test := range tests { 52 | t.Run(test.name, func(t *testing.T) { 53 | called := false 54 | inner := &fake{ 55 | isr: func(ip string) error { 56 | called = true 57 | if ip != test.input { 58 | t.Errorf("ISR = %v, wanted %v", ip, test.input) 59 | } 60 | return nil 61 | }, 62 | } 63 | rec := &Recorder{ 64 | Builder: inner, 65 | } 66 | rec.IsSupportedReference(test.input) 67 | if !called { 68 | t.Error("IsSupportedReference wasn't called, wanted called") 69 | } 70 | }) 71 | } 72 | } 73 | 74 | func TestBuildRecording(t *testing.T) { 75 | tests := []struct { 76 | name string 77 | inputs []string 78 | }{{ 79 | name: "no calls", 80 | }, { 81 | name: "one call", 82 | inputs: []string{ 83 | "github.com/foo/bar", 84 | }, 85 | }, { 86 | name: "two calls", 87 | inputs: []string{ 88 | "github.com/foo/bar", 89 | "github.com/foo/baz", 90 | }, 91 | }, { 92 | name: "duplicates", 93 | inputs: []string{ 94 | "github.com/foo/bar", 95 | "github.com/foo/baz", 96 | "github.com/foo/bar", 97 | "github.com/foo/baz", 98 | }, 99 | }} 100 | 101 | for _, test := range tests { 102 | t.Run(test.name, func(t *testing.T) { 103 | inner := &fake{ 104 | b: func(_ string) (Result, error) { 105 | return nil, nil 106 | }, 107 | } 108 | rec := &Recorder{ 109 | Builder: inner, 110 | } 111 | for _, in := range test.inputs { 112 | rec.Build(context.Background(), in) 113 | } 114 | if diff := cmp.Diff(test.inputs, rec.ImportPaths); diff != "" { 115 | t.Errorf("Build (-want, +got): %s", diff) 116 | } 117 | }) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /pkg/build/shared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package build 16 | 17 | import ( 18 | "context" 19 | "sync" 20 | ) 21 | 22 | // Caching wraps a builder implementation in a layer that shares build results 23 | // for the same inputs using a simple "future" implementation. Cached results 24 | // may be invalidated by calling Invalidate with the same input passed to Build. 25 | type Caching struct { 26 | inner Interface 27 | 28 | m sync.Mutex 29 | results map[string]*future 30 | } 31 | 32 | // Caching implements Interface 33 | var _ Interface = (*Caching)(nil) 34 | 35 | // NewCaching wraps the provided build.Interface in an implementation that 36 | // shares build results for a given path until the result has been invalidated. 37 | func NewCaching(inner Interface) (*Caching, error) { 38 | return &Caching{ 39 | inner: inner, 40 | results: make(map[string]*future), 41 | }, nil 42 | } 43 | 44 | // Build implements Interface 45 | func (c *Caching) Build(ctx context.Context, ip string) (Result, error) { 46 | f := func() *future { 47 | // Lock the map of futures. 48 | c.m.Lock() 49 | defer c.m.Unlock() 50 | 51 | // If a future for "ip" exists, then return it. 52 | f, ok := c.results[ip] 53 | if ok { 54 | return f 55 | } 56 | // Otherwise create and record a future for a Build of "ip". 57 | f = newFuture(func() (Result, error) { 58 | return c.inner.Build(ctx, ip) 59 | }) 60 | c.results[ip] = f 61 | return f 62 | }() 63 | 64 | return f.Get() 65 | } 66 | 67 | // QualifyImport implements Interface 68 | func (c *Caching) QualifyImport(ip string) (string, error) { 69 | return c.inner.QualifyImport(ip) 70 | } 71 | 72 | // IsSupportedReference implements Interface 73 | func (c *Caching) IsSupportedReference(ip string) error { 74 | return c.inner.IsSupportedReference(ip) 75 | } 76 | 77 | // Invalidate removes an import path's cached results. 78 | func (c *Caching) Invalidate(ip string) { 79 | c.m.Lock() 80 | defer c.m.Unlock() 81 | 82 | delete(c.results, ip) 83 | } 84 | -------------------------------------------------------------------------------- /pkg/build/shared_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package build 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | "time" 21 | 22 | "github.com/google/go-containerregistry/pkg/v1/random" 23 | ) 24 | 25 | type slowbuild struct { 26 | sleep time.Duration 27 | } 28 | 29 | // slowbuild implements Interface 30 | var _ Interface = (*slowbuild)(nil) 31 | 32 | func (sb *slowbuild) QualifyImport(ip string) (string, error) { return ip, nil } 33 | 34 | func (sb *slowbuild) IsSupportedReference(string) error { return nil } 35 | 36 | func (sb *slowbuild) Build(context.Context, string) (Result, error) { 37 | time.Sleep(sb.sleep) 38 | return random.Index(256, 8, 3) 39 | } 40 | 41 | func TestCaching(t *testing.T) { 42 | duration := 100 * time.Millisecond 43 | ip := "foo" 44 | 45 | sb := &slowbuild{duration} 46 | cb, _ := NewCaching(sb) 47 | 48 | if err := cb.IsSupportedReference(ip); err != nil { 49 | t.Errorf("ISR(%q) = (%v), wanted nil", err, ip) 50 | } 51 | 52 | previousDigest := "not-a-digest" 53 | // Each iteration, we test that the first build is slow and subsequent 54 | // builds are fast and return the same image. Then we invalidate the 55 | // cache and iterate. 56 | for idx := 0; idx < 3; idx++ { 57 | start := time.Now() 58 | img1, err := cb.Build(context.Background(), ip) 59 | if err != nil { 60 | t.Errorf("Build() = %v", err) 61 | } 62 | end := time.Now() 63 | 64 | elapsed := end.Sub(start) 65 | if elapsed < duration { 66 | t.Errorf("Elapsed time %v, wanted >= %s", elapsed, duration) 67 | } 68 | d1 := digest(t, img1) 69 | 70 | if d1 == previousDigest { 71 | t.Errorf("Got same digest as previous iteration, wanted different: %v", d1) 72 | } 73 | previousDigest = d1 74 | 75 | start = time.Now() 76 | img2, err := cb.Build(context.Background(), ip) 77 | if err != nil { 78 | t.Errorf("Build() = %v", err) 79 | } 80 | end = time.Now() 81 | 82 | elapsed = end.Sub(start) 83 | if elapsed >= duration { 84 | t.Errorf("Elapsed time %v, wanted < %s", elapsed, duration) 85 | } 86 | d2 := digest(t, img2) 87 | 88 | if d1 != d2 { 89 | t.Error("Got different images, wanted same") 90 | } 91 | 92 | cb.Invalidate(ip) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pkg/build/strict.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package build 16 | 17 | import "strings" 18 | 19 | // StrictScheme is a prefix that can be placed on import paths that users 20 | // think MUST be supported references. 21 | const StrictScheme = "ko://" 22 | 23 | type reference struct { 24 | strict bool 25 | path string 26 | } 27 | 28 | func newRef(s string) reference { 29 | return reference{ 30 | strict: strings.HasPrefix(s, StrictScheme), 31 | path: strings.TrimPrefix(s, StrictScheme), 32 | } 33 | } 34 | 35 | func (r reference) IsStrict() bool { 36 | return r.strict 37 | } 38 | 39 | func (r reference) Path() string { 40 | return r.path 41 | } 42 | 43 | func (r reference) String() string { 44 | if r.IsStrict() { 45 | return StrictScheme + r.Path() 46 | } 47 | return r.Path() 48 | } 49 | -------------------------------------------------------------------------------- /pkg/build/strict_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package build 16 | 17 | import "testing" 18 | 19 | func TestStrictReference(t *testing.T) { 20 | tests := []struct { 21 | name string 22 | input string 23 | strict bool 24 | path string 25 | }{{ 26 | name: "loose", 27 | input: "github.com/foo/bar", 28 | strict: false, 29 | path: "github.com/foo/bar", 30 | }, { 31 | name: "strict", 32 | input: "ko://github.com/foo/bar", 33 | strict: true, 34 | path: "github.com/foo/bar", 35 | }} 36 | 37 | for _, test := range tests { 38 | t.Run(test.name, func(t *testing.T) { 39 | ref := newRef(test.input) 40 | if got, want := ref.IsStrict(), test.strict; got != want { 41 | t.Errorf("got: %v, want: %v", got, want) 42 | } 43 | if got, want := ref.Path(), test.path; got != want { 44 | t.Errorf("got: %v, want: %v", got, want) 45 | } 46 | if got, want := ref.String(), test.input; got != want { 47 | t.Errorf("got: %v, want: %v", got, want) 48 | } 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pkg/caps/caps_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caps 16 | 17 | import ( 18 | "encoding/base64" 19 | "fmt" 20 | "testing" 21 | ) 22 | 23 | func TestParse(t *testing.T) { 24 | tests := []struct { 25 | arg string 26 | res Mask 27 | mustFail bool 28 | }{ 29 | {arg: "chown", res: 1}, 30 | {arg: "cap_chown", res: 1}, 31 | {arg: "cAp_cHoWn", res: 1}, 32 | {arg: "unknown", mustFail: true}, 33 | {arg: "63", res: 1 << 63}, 34 | {arg: "64", mustFail: true}, 35 | {arg: "all", res: allKnownCaps()}, 36 | } 37 | for _, tc := range tests { 38 | t.Run(tc.arg, func(t *testing.T) { 39 | mask, err := Parse(tc.arg) 40 | if err == nil && tc.mustFail { 41 | t.Fatal("invalid input accepted") 42 | } 43 | if err != nil && !tc.mustFail { 44 | t.Fatal(err) 45 | } 46 | if mask != tc.res { 47 | t.Fatalf("unexpected result: %x", mask) 48 | } 49 | }) 50 | } 51 | } 52 | 53 | //go:generate ./gen.sh 54 | 55 | type ddTest struct { 56 | permitted, inheritable string 57 | effective bool 58 | res string 59 | } 60 | 61 | func TestDd(t *testing.T) { 62 | for _, test := range ddTests { 63 | label := fmt.Sprintf("%s,%s,%v", test.permitted, test.inheritable, test.effective) 64 | t.Run(label, func(t *testing.T) { 65 | var permitted, inheritable Mask 66 | var flags Flags 67 | 68 | if test.permitted != "" { 69 | mask, err := Parse(test.permitted) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | permitted = mask 74 | } 75 | 76 | if test.inheritable != "" { 77 | mask, err := Parse(test.inheritable) 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | inheritable = mask 82 | } 83 | 84 | if test.effective { 85 | flags = FlagEffective 86 | } 87 | 88 | res, err := XattrBytes(permitted, inheritable, flags) 89 | if err != nil { 90 | t.Fatal(err) 91 | } 92 | 93 | resBase64 := make([]byte, base64.StdEncoding.EncodedLen(len(res))) 94 | base64.StdEncoding.Encode(resBase64, res) 95 | if string(resBase64) != test.res { 96 | t.Fatalf("expected %s, result %s", test.res, resBase64) 97 | } 98 | }) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /pkg/caps/gen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2024 ko Build Authors All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This script assigns different capabilities to files and captures 18 | # resulting xattr blobs for testing (generates caps_dd_test.go). 19 | # 20 | # It has to be run on a reasonably recent Linux to ensure that the full 21 | # set of capabilities is supported. Setting capabilities requires 22 | # privileges; the script assumes paswordless sudo is available. 23 | 24 | set -o errexit 25 | set -o nounset 26 | set -o pipefail 27 | shopt -s inherit_errexit 28 | 29 | # capblob CAP_STRING 30 | # Obtain base64-encoded value of the underlying xattr that implemens 31 | # specified capabilities, setcap syntax. 32 | # Example: capblob cap_chown=eip 33 | capblob() { 34 | f=$(mktemp) 35 | sudo -n setcap $1 $f 36 | getfattr -n security.capability --absolute-names --only-values $f | base64 37 | rm $f 38 | } 39 | 40 | ( 41 | license=$(sed -e '/^$/,$d' caps.go) 42 | 43 | echo "// Generated file, do not edit." 44 | echo "" 45 | echo "$license" 46 | echo "" 47 | echo "package caps" 48 | echo "var ddTests = []ddTest{" 49 | 50 | res=$(capblob cap_chown=p) 51 | echo "{permitted: \"chown\", inheritable: \"\", effective: false, res: \"$res\"}," 52 | 53 | res=$(capblob cap_chown=ep) 54 | echo "{permitted: \"chown\", inheritable: \"\", effective: true, res: \"$res\"}," 55 | 56 | res=$(capblob cap_chown=i) 57 | echo "{permitted: \"\", inheritable: \"chown\", effective: false, res: \"$res\"}," 58 | 59 | CAPS="chown dac_override dac_read_search fowner fsetid kill setgid setuid 60 | setpcap linux_immutable net_bind_service net_broadcast net_admin net_raw ipc_lock ipc_owner 61 | sys_module sys_rawio sys_chroot sys_ptrace sys_pacct sys_admin sys_boot sys_nice 62 | sys_resource sys_time sys_tty_config mknod lease audit_write audit_control setfcap 63 | mac_override mac_admin syslog wake_alarm block_suspend audit_read perfmon bpf 64 | checkpoint_restore" 65 | for cap in $CAPS; do 66 | res=$(capblob cap_$cap=eip) 67 | echo "{permitted: \"$cap\", inheritable: \"$cap\", effective: true, res: \"$res\"}," 68 | done 69 | 70 | echo "}" 71 | ) > caps_dd_test.go 72 | 73 | gofmt -w -s ./caps_dd_test.go 74 | -------------------------------------------------------------------------------- /pkg/caps/new_file_caps_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caps 16 | 17 | import ( 18 | "reflect" 19 | "strings" 20 | "testing" 21 | ) 22 | 23 | func TestNewFileCaps(t *testing.T) { 24 | tests := []struct { 25 | args []string 26 | res *FileCaps 27 | mustFail bool 28 | }{ 29 | {}, 30 | { 31 | args: []string{"chown", "dac_override", "dac_read_search"}, 32 | res: &FileCaps{permitted: 7}, 33 | }, 34 | { 35 | args: []string{"chown,dac_override,dac_read_search=p"}, 36 | res: &FileCaps{permitted: 7}, 37 | }, 38 | { 39 | args: []string{"chown,dac_override,dac_read_search=i"}, 40 | res: &FileCaps{inheritable: 7}, 41 | }, 42 | { 43 | args: []string{"chown,dac_override,dac_read_search=e"}, 44 | }, 45 | { 46 | args: []string{"chown,dac_override,dac_read_search=pe"}, 47 | res: &FileCaps{permitted: 7, flags: FlagEffective}, 48 | }, 49 | { 50 | args: []string{"=pe"}, 51 | res: &FileCaps{permitted: allKnownCaps(), flags: FlagEffective}, 52 | }, 53 | { 54 | args: []string{"chown=ie", "chown=p"}, 55 | res: &FileCaps{permitted: 1}, 56 | }, 57 | { 58 | args: []string{"chown=ie", "chown="}, 59 | }, 60 | { 61 | args: []string{"chown=ie", "chown+p"}, 62 | res: &FileCaps{permitted: 1, inheritable: 1, flags: FlagEffective}, 63 | }, 64 | { 65 | args: []string{"chown=pie", "dac_override,chown-p"}, 66 | res: &FileCaps{inheritable: 1, flags: FlagEffective}, 67 | }, 68 | {args: []string{"chown,=pie"}, mustFail: true}, 69 | {args: []string{"-pie"}, mustFail: true}, 70 | {args: []string{"+pie"}, mustFail: true}, 71 | {args: []string{"="}}, 72 | } 73 | for _, tc := range tests { 74 | label := strings.Join(tc.args, ":") 75 | t.Run(label, func(t *testing.T) { 76 | res, err := NewFileCaps(tc.args...) 77 | if tc.mustFail && err == nil { 78 | t.Fatal("didn't fail") 79 | } 80 | if !tc.mustFail && err != nil { 81 | t.Fatalf("unexpectedly failed: %v", err) 82 | } 83 | if !reflect.DeepEqual(res, tc.res) { 84 | t.Fatalf("got %v expected %v", res, tc.res) 85 | } 86 | }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pkg/commands/commands.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package commands 16 | 17 | import ( 18 | "os/exec" 19 | 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | // AddKubeCommands augments our CLI surface with a passthru delete command, and an apply 24 | // command that realizes the promise of ko, as outlined here: 25 | // 26 | // https://github.com/google/go-containerregistry/issues/80 27 | func AddKubeCommands(topLevel *cobra.Command) { 28 | addDelete(topLevel) 29 | addVersion(topLevel) 30 | addCreate(topLevel) 31 | addApply(topLevel) 32 | addResolve(topLevel) 33 | addBuild(topLevel) 34 | addRun(topLevel) 35 | } 36 | 37 | // check if kubectl is installed 38 | func isKubectlAvailable() bool { 39 | if _, err := exec.LookPath("kubectl"); err != nil { 40 | return false 41 | } 42 | return true 43 | } 44 | -------------------------------------------------------------------------------- /pkg/commands/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package commands 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "testing" 21 | 22 | "github.com/google/go-containerregistry/pkg/crane" 23 | 24 | "github.com/google/ko/pkg/commands/options" 25 | ) 26 | 27 | func TestOverrideDefaultBaseImageUsingBuildOption(t *testing.T) { 28 | namespace := "base" 29 | s, err := registryServerWithImage(namespace) 30 | if err != nil { 31 | t.Fatalf("could not create test registry server: %v", err) 32 | } 33 | defer s.Close() 34 | baseImage := fmt.Sprintf("%s/%s", s.Listener.Addr().String(), namespace) 35 | wantDigest, err := crane.Digest(baseImage) 36 | if err != nil { 37 | t.Fatalf("crane.Digest(%s): %v", baseImage, err) 38 | } 39 | wantImage := fmt.Sprintf("%s@%s", baseImage, wantDigest) 40 | bo := &options.BuildOptions{ 41 | BaseImage: wantImage, 42 | Platforms: []string{"all"}, 43 | } 44 | 45 | baseFn := getBaseImage(bo) 46 | _, res, err := baseFn(context.Background(), "ko://example.com/helloworld") 47 | if err != nil { 48 | t.Fatalf("getBaseImage(): %v", err) 49 | } 50 | 51 | digest, err := res.Digest() 52 | if err != nil { 53 | t.Fatalf("res.Digest(): %v", err) 54 | } 55 | gotDigest := digest.String() 56 | if gotDigest != wantDigest { 57 | t.Errorf("got digest %s, wanted %s", gotDigest, wantDigest) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pkg/commands/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package commands 16 | 17 | import ( 18 | "errors" 19 | "os" 20 | "os/exec" 21 | 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | // runCmd is suitable for use with cobra.Command's Run field. 26 | type runCmd func(*cobra.Command, []string) error 27 | 28 | // passthru returns a runCmd that simply passes our CLI arguments 29 | // through to a binary named command. 30 | func passthru(command string) runCmd { 31 | return func(cmd *cobra.Command, _ []string) error { 32 | if !isKubectlAvailable() { 33 | return errors.New("error: kubectl is not available. kubectl must be installed to use ko delete") 34 | } 35 | ctx := cmd.Context() 36 | 37 | // Start building a command line invocation by passing 38 | // through our arguments to command's CLI. 39 | //nolint:gosec // We actively want to pass arguments through, so this is fine. 40 | ecmd := exec.CommandContext(ctx, command, os.Args[1:]...) 41 | 42 | // Pass through our environment 43 | ecmd.Env = os.Environ() 44 | // Pass through our stdfoo 45 | ecmd.Stderr = os.Stderr 46 | ecmd.Stdout = os.Stdout 47 | ecmd.Stdin = os.Stdin 48 | 49 | // Run it. 50 | return ecmd.Run() 51 | } 52 | } 53 | 54 | // addDelete augments our CLI surface with publish. 55 | func addDelete(topLevel *cobra.Command) { 56 | topLevel.AddCommand(&cobra.Command{ 57 | Use: "delete", 58 | Short: `See "kubectl help delete" for detailed usage.`, 59 | RunE: passthru("kubectl"), 60 | // We ignore unknown flags to avoid importing everything Go exposes 61 | // from our commands. 62 | FParseErrWhitelist: cobra.FParseErrWhitelist{ 63 | UnknownFlags: true, 64 | }, 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /pkg/commands/options/filestuff.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package options 16 | 17 | import ( 18 | "log" 19 | "os" 20 | "path/filepath" 21 | 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | // FilenameOptions is from pkg/kubectl. 26 | type FilenameOptions struct { 27 | Filenames []string 28 | Recursive bool 29 | } 30 | 31 | func AddFileArg(cmd *cobra.Command, fo *FilenameOptions) { 32 | // From pkg/kubectl 33 | cmd.Flags().StringSliceVarP(&fo.Filenames, "filename", "f", fo.Filenames, 34 | "Filename, directory, or URL to files to use to create the resource") 35 | cmd.Flags().BoolVarP(&fo.Recursive, "recursive", "R", fo.Recursive, 36 | "Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.") 37 | } 38 | 39 | // Based heavily on pkg/kubectl 40 | func EnumerateFiles(fo *FilenameOptions) chan string { 41 | files := make(chan string) 42 | go func() { 43 | // When we're done enumerating files, close the channel 44 | defer close(files) 45 | for _, paths := range fo.Filenames { 46 | // Just pass through '-' as it is indicative of stdin. 47 | if paths == "-" { 48 | files <- paths 49 | continue 50 | } 51 | // For each of the "filenames" we are passed (file or directory) start a 52 | // "Walk" to enumerate all of the contained files recursively. 53 | err := filepath.Walk(paths, func(path string, fi os.FileInfo, err error) error { 54 | if err != nil { 55 | return err 56 | } 57 | 58 | // If this is a directory, skip it if it isn't the current directory we are 59 | // processing (unless we are in recursive mode). If we decide to process 60 | // the directory, and we're in watch mode, then we set up a watch on the 61 | // directory. 62 | if fi.IsDir() { 63 | if path != paths && !fo.Recursive { 64 | return filepath.SkipDir 65 | } 66 | // We don't stream back directories, we just decide to skip them, or not. 67 | return nil 68 | } 69 | 70 | // Don't check extension if the filepath was passed explicitly 71 | if path != paths { 72 | switch filepath.Ext(path) { 73 | case ".json", ".yaml": 74 | // Process these. 75 | default: 76 | return nil 77 | } 78 | } 79 | 80 | files <- path 81 | return nil 82 | }) 83 | if err != nil { 84 | log.Fatalf("Error enumerating files: %v", err) 85 | } 86 | } 87 | }() 88 | return files 89 | } 90 | -------------------------------------------------------------------------------- /pkg/commands/options/namer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package options_test 16 | 17 | import ( 18 | "path" 19 | "testing" 20 | 21 | "github.com/google/ko/pkg/commands/options" 22 | ) 23 | 24 | func TestMakeNamer(t *testing.T) { 25 | foreachTestCaseMakeNamer(func(tc testMakeNamerCase) { 26 | t.Run(tc.name, func(t *testing.T) { 27 | namer := options.MakeNamer(&tc.opts) 28 | got := namer("registry.example.org/foo/bar", "example.org/sample/cmd/example") 29 | 30 | if got != tc.want { 31 | t.Errorf("got image name %s, wanted %s", got, tc.want) 32 | } 33 | }) 34 | }) 35 | } 36 | 37 | func foreachTestCaseMakeNamer(fn func(tc testMakeNamerCase)) { 38 | for _, namerCase := range testMakeNamerCases() { 39 | fn(namerCase) 40 | } 41 | } 42 | 43 | func testMakeNamerCases() []testMakeNamerCase { 44 | return []testMakeNamerCase{{ 45 | name: "defaults", 46 | want: "registry.example.org/foo/bar/example-51d74b7127c5f7495a338df33ecdeb19", 47 | }, { 48 | name: "with preserve import paths", 49 | want: "registry.example.org/foo/bar/example.org/sample/cmd/example", 50 | opts: options.PublishOptions{PreserveImportPaths: true}, 51 | }, { 52 | name: "with base import paths", 53 | want: "registry.example.org/foo/bar/example", 54 | opts: options.PublishOptions{BaseImportPaths: true}, 55 | }, { 56 | name: "with bare", 57 | want: "registry.example.org/foo/bar", 58 | opts: options.PublishOptions{Bare: true}, 59 | }, { 60 | name: "with custom namer", 61 | want: "registry.example.org/foo/bar-example", 62 | opts: options.PublishOptions{ImageNamer: func(base string, importpath string) string { 63 | return base + "-" + path.Base(importpath) 64 | }}, 65 | }} 66 | } 67 | 68 | type testMakeNamerCase struct { 69 | name string 70 | opts options.PublishOptions 71 | want string 72 | } 73 | -------------------------------------------------------------------------------- /pkg/commands/options/selector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package options 16 | 17 | import ( 18 | "github.com/spf13/cobra" 19 | ) 20 | 21 | // SelectorOptions allows selecting objects from the input manifests by label 22 | type SelectorOptions struct { 23 | Selector string 24 | } 25 | 26 | func AddSelectorArg(cmd *cobra.Command, so *SelectorOptions) { 27 | cmd.Flags().StringVarP(&so.Selector, "selector", "l", "", 28 | "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/commands/options/testdata/bad-config/.ko.yaml/.gitignore: -------------------------------------------------------------------------------- 1 | .# We just want to have a practically empty directory. 2 | * 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /pkg/commands/options/testdata/config/.ko.yaml: -------------------------------------------------------------------------------- 1 | defaultBaseImage: alpine 2 | defaultPlatforms: all 3 | defaultEnv: FOO=bar 4 | defaultFlags: 5 | - -tags 6 | - netgo 7 | defaultLdflags: 8 | - -s -w 9 | -------------------------------------------------------------------------------- /pkg/commands/options/testdata/config/my-ko.yaml: -------------------------------------------------------------------------------- 1 | defaultBaseImage: wow 2 | -------------------------------------------------------------------------------- /pkg/commands/options/testdata/multiple-platforms/.ko.yaml: -------------------------------------------------------------------------------- 1 | defaultBaseImage: alpine 2 | defaultPlatforms: 3 | - linux/arm64 4 | - linux/amd64 5 | -------------------------------------------------------------------------------- /pkg/commands/options/testdata/paths/.ko.yaml: -------------------------------------------------------------------------------- 1 | builds: 2 | - id: app-with-main-package-in-different-directory-to-go-mod-and-ko-yaml 3 | dir: ./app 4 | main: ./cmd/foo 5 | -------------------------------------------------------------------------------- /pkg/commands/options/testdata/paths/app/cmd/foo/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import "fmt" 18 | 19 | func main() { 20 | fmt.Println("cmd/foo") 21 | } 22 | -------------------------------------------------------------------------------- /pkg/commands/options/testdata/paths/app/go.mod: -------------------------------------------------------------------------------- 1 | module example.com/testapp 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /pkg/commands/options/validate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package options 16 | 17 | import ( 18 | "errors" 19 | "log" 20 | "strings" 21 | ) 22 | 23 | const bareBaseFlagsWarning = `WARNING! 24 | ----------------------------------------------------------------- 25 | Both --base-import-paths and --bare were set. 26 | 27 | --base-import-paths will take precedence and ignore --bare flag. 28 | 29 | In a future release this will be an error. 30 | ----------------------------------------------------------------- 31 | ` 32 | 33 | const localFlagsWarning = `WARNING! 34 | ----------------------------------------------------------------- 35 | The --local flag is set and KO_DOCKER_REPO is set to ko.local 36 | 37 | You can choose either one to build a local image. 38 | 39 | The --local flag might be deprecated in the future. 40 | ----------------------------------------------------------------- 41 | ` 42 | 43 | func Validate(po *PublishOptions, bo *BuildOptions) error { 44 | po.Jobs = bo.ConcurrentBuilds 45 | if po.Bare && po.BaseImportPaths { 46 | log.Print(bareBaseFlagsWarning) 47 | // TODO: return error when we decided to make this an error, for now it is a warning 48 | } 49 | 50 | if po.Local && strings.Contains(po.DockerRepo, "ko.local") { 51 | log.Print(localFlagsWarning) 52 | } 53 | 54 | if len(bo.Platforms) > 1 { 55 | for _, platform := range bo.Platforms { 56 | if platform == "all" { 57 | return errors.New("all or specific platforms should be used") 58 | } 59 | } 60 | } 61 | 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /pkg/commands/publisher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package commands 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | "github.com/google/go-containerregistry/pkg/name" 22 | "github.com/google/ko/pkg/build" 23 | "github.com/google/ko/pkg/publish" 24 | ) 25 | 26 | // PublishImages publishes images 27 | func PublishImages(ctx context.Context, importpaths []string, pub publish.Interface, b build.Interface) (map[string]name.Reference, error) { 28 | return publishImages(ctx, importpaths, pub, b) 29 | } 30 | 31 | func publishImages(ctx context.Context, importpaths []string, pub publish.Interface, b build.Interface) (map[string]name.Reference, error) { 32 | imgs := make(map[string]name.Reference) 33 | for _, importpath := range importpaths { 34 | importpath, err := b.QualifyImport(importpath) 35 | if err != nil { 36 | return nil, err 37 | } 38 | if err := b.IsSupportedReference(importpath); err != nil { 39 | return nil, fmt.Errorf("importpath %q is not supported: %w", importpath, err) 40 | } 41 | 42 | img, err := b.Build(ctx, importpath) 43 | if err != nil { 44 | return nil, fmt.Errorf("error building %q: %w", importpath, err) 45 | } 46 | ref, err := pub.Publish(ctx, img, importpath) 47 | if err != nil { 48 | return nil, fmt.Errorf("error publishing %s: %w", importpath, err) 49 | } 50 | imgs[importpath] = ref 51 | } 52 | return imgs, nil 53 | } 54 | -------------------------------------------------------------------------------- /pkg/commands/resolve.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package commands 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/google/ko/pkg/commands/options" 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | // addResolve augments our CLI surface with resolve. 26 | func addResolve(topLevel *cobra.Command) { 27 | po := &options.PublishOptions{} 28 | fo := &options.FilenameOptions{} 29 | so := &options.SelectorOptions{} 30 | bo := &options.BuildOptions{} 31 | 32 | resolve := &cobra.Command{ 33 | Use: "resolve -f FILENAME", 34 | Short: "Print the input files with image references resolved to built/pushed image digests.", 35 | Long: `This sub-command finds import path references within the provided files, builds them into Go binaries, containerizes them, publishes them, and prints the resulting yaml.`, 36 | Example: ` 37 | # Build and publish import path references to a Docker 38 | # Registry as: 39 | # ${KO_DOCKER_REPO}/- 40 | # When KO_DOCKER_REPO is ko.local, it is the same as if 41 | # --local and --preserve-import-paths were passed. 42 | ko resolve -f config/ 43 | 44 | # Build and publish import path references to a Docker 45 | # Registry preserving import path names as: 46 | # ${KO_DOCKER_REPO}/ 47 | # When KO_DOCKER_REPO is ko.local, it is the same as if 48 | # --local was passed. 49 | ko resolve --preserve-import-paths -f config/ 50 | 51 | # Build and publish import path references to a Docker 52 | # daemon as: 53 | # ko.local/ 54 | # This always preserves import paths. 55 | ko resolve --local -f config/`, 56 | Args: cobra.NoArgs, 57 | RunE: func(cmd *cobra.Command, _ []string) error { 58 | if err := options.Validate(po, bo); err != nil { 59 | return fmt.Errorf("validating options: %w", err) 60 | } 61 | 62 | ctx := cmd.Context() 63 | 64 | bo.InsecureRegistry = po.InsecureRegistry 65 | builder, err := makeBuilder(ctx, bo) 66 | if err != nil { 67 | return fmt.Errorf("error creating builder: %w", err) 68 | } 69 | publisher, err := makePublisher(po) 70 | if err != nil { 71 | return fmt.Errorf("error creating publisher: %w", err) 72 | } 73 | defer publisher.Close() 74 | return ResolveFilesToWriter(ctx, builder, publisher, fo, so, os.Stdout) 75 | }, 76 | } 77 | options.AddPublishArg(resolve, po) 78 | options.AddFileArg(resolve, fo) 79 | options.AddSelectorArg(resolve, so) 80 | options.AddBuildOptions(resolve, bo) 81 | topLevel.AddCommand(resolve) 82 | } 83 | -------------------------------------------------------------------------------- /pkg/commands/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package commands 16 | 17 | import ( 18 | "os" 19 | 20 | cranecmd "github.com/google/go-containerregistry/cmd/crane/cmd" 21 | "github.com/google/go-containerregistry/pkg/logs" 22 | "github.com/spf13/cobra" 23 | "go.uber.org/automaxprocs/maxprocs" 24 | ) 25 | 26 | var Root = New() 27 | 28 | func New() *cobra.Command { 29 | var verbose bool 30 | root := &cobra.Command{ 31 | Use: "ko", 32 | Short: "Rapidly iterate with Go, Containers, and Kubernetes.", 33 | SilenceUsage: true, // Don't show usage on errors 34 | DisableAutoGenTag: true, 35 | PersistentPreRun: func(_ *cobra.Command, _ []string) { 36 | if verbose { 37 | logs.Warn.SetOutput(os.Stderr) 38 | logs.Debug.SetOutput(os.Stderr) 39 | } 40 | logs.Progress.SetOutput(os.Stderr) 41 | 42 | maxprocs.Set(maxprocs.Logger(logs.Debug.Printf)) 43 | }, 44 | Run: func(cmd *cobra.Command, _ []string) { 45 | cmd.Help() 46 | }, 47 | } 48 | root.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable debug logs") 49 | 50 | AddKubeCommands(root) 51 | 52 | // Also add the auth group from crane to facilitate logging into a 53 | // registry. 54 | authCmd := cranecmd.NewCmdAuth(nil, "ko", "auth") 55 | // That was a mistake, but just set it to Hidden so we don't break people. 56 | authCmd.Hidden = true 57 | root.AddCommand(authCmd) 58 | 59 | // Just add a `ko login` command: 60 | root.AddCommand(cranecmd.NewCmdAuthLogin("ko")) 61 | return root 62 | } 63 | -------------------------------------------------------------------------------- /pkg/commands/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package commands 16 | 17 | import ( 18 | "fmt" 19 | "runtime/debug" 20 | 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | // Version is provided by govvv at compile-time 25 | var Version string 26 | 27 | // addVersion augments our CLI surface with version. 28 | func addVersion(topLevel *cobra.Command) { 29 | topLevel.AddCommand(&cobra.Command{ 30 | Use: "version", 31 | Short: `Print ko version.`, 32 | Run: func(_ *cobra.Command, _ []string) { 33 | v := version() 34 | if v == "" { 35 | fmt.Println("could not determine build information") 36 | } else { 37 | fmt.Println(v) 38 | } 39 | }, 40 | }) 41 | } 42 | 43 | func version() string { 44 | if Version == "" { 45 | i, ok := debug.ReadBuildInfo() 46 | if !ok { 47 | return "" 48 | } 49 | Version = i.Main.Version 50 | } 51 | return Version 52 | } 53 | -------------------------------------------------------------------------------- /pkg/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package ko holds libraries used to implement the ko CLI. 16 | package ko 17 | -------------------------------------------------------------------------------- /pkg/internal/git/clone.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // MIT License 16 | // 17 | // Copyright (c) 2016-2022 Carlos Alexandro Becker 18 | // 19 | // Permission is hereby granted, free of charge, to any person obtaining a copy 20 | // of this software and associated documentation files (the "Software"), to deal 21 | // in the Software without restriction, including without limitation the rights 22 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | // copies of the Software, and to permit persons to whom the Software is 24 | // furnished to do so, subject to the following conditions: 25 | // 26 | // The above copyright notice and this permission notice shall be included in all 27 | // copies or substantial portions of the Software. 28 | // 29 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | // SOFTWARE. 36 | 37 | package git 38 | 39 | import ( 40 | "context" 41 | "fmt" 42 | "os/exec" 43 | ) 44 | 45 | // Clone the git repository from the repoURL to the specified dir. 46 | func Clone(ctx context.Context, dir string, repoURL string) error { 47 | rc := runConfig{ 48 | dir: dir, 49 | args: []string{"clone", "--depth", "1", repoURL, "."}, 50 | } 51 | 52 | cmd := exec.CommandContext(ctx, "git", "clone", repoURL, dir) 53 | cmd.Dir = dir 54 | 55 | _, err := run(ctx, rc) 56 | if err != nil { 57 | return fmt.Errorf("running git clone: %w", err) 58 | } 59 | 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /pkg/internal/git/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // MIT License 16 | // 17 | // Copyright (c) 2016-2022 Carlos Alexandro Becker 18 | // 19 | // Permission is hereby granted, free of charge, to any person obtaining a copy 20 | // of this software and associated documentation files (the "Software"), to deal 21 | // in the Software without restriction, including without limitation the rights 22 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | // copies of the Software, and to permit persons to whom the Software is 24 | // furnished to do so, subject to the following conditions: 25 | // 26 | // The above copyright notice and this permission notice shall be included in all 27 | // copies or substantial portions of the Software. 28 | // 29 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | // SOFTWARE. 36 | 37 | package git 38 | 39 | import ( 40 | "errors" 41 | "fmt" 42 | ) 43 | 44 | var ( 45 | // ErrNoTag happens if the underlying git repository doesn't contain any tags 46 | // but no snapshot-release was requested. 47 | ErrNoTag = errors.New("git doesn't contain any tags. Tag info will not be available") 48 | 49 | // ErrNotRepository happens if you try to run ko against a folder 50 | // which is not a git repository. 51 | ErrNotRepository = errors.New("current folder is not a git repository. Git info will not be available") 52 | 53 | // ErrNoGit happens when git is not present in PATH. 54 | ErrNoGit = errors.New("git not present in PATH. Git info will not be available") 55 | ) 56 | 57 | // ErrDirty happens when the repo has uncommitted/unstashed changes. 58 | type ErrDirty struct { 59 | status string 60 | } 61 | 62 | func (e ErrDirty) Error() string { 63 | return fmt.Sprintf("git is in a dirty state\nPlease check in your pipeline what can be changing the following files:\n%v\n", e.status) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/internal/gittesting/git_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // MIT License 16 | // 17 | // Copyright (c) 2016-2022 Carlos Alexandro Becker 18 | // 19 | // Permission is hereby granted, free of charge, to any person obtaining a copy 20 | // of this software and associated documentation files (the "Software"), to deal 21 | // in the Software without restriction, including without limitation the rights 22 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | // copies of the Software, and to permit persons to whom the Software is 24 | // furnished to do so, subject to the following conditions: 25 | // 26 | // The above copyright notice and this permission notice shall be included in all 27 | // copies or substantial portions of the Software. 28 | // 29 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | // SOFTWARE. 36 | 37 | package gittesting 38 | 39 | import "testing" 40 | 41 | func TestGit(t *testing.T) { 42 | dir := t.TempDir() 43 | GitInit(t, dir) 44 | GitAdd(t, dir) 45 | GitCommit(t, dir, "commit1") 46 | GitRemoteAdd(t, dir, "git@github.com:goreleaser/nope.git") 47 | GitTag(t, dir, "v1.0.0") 48 | } 49 | -------------------------------------------------------------------------------- /pkg/internal/testing/daemon.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package testing 16 | 17 | import ( 18 | "context" 19 | "io" 20 | "strings" 21 | 22 | "github.com/docker/docker/api/types/image" 23 | "github.com/docker/docker/client" 24 | "github.com/google/go-containerregistry/pkg/v1/daemon" 25 | ) 26 | 27 | type MockDaemon struct { 28 | daemon.Client 29 | Tags []string 30 | 31 | inspectErr error 32 | inspectResp image.InspectResponse 33 | inspectBody []byte 34 | } 35 | 36 | func (m *MockDaemon) NegotiateAPIVersion(context.Context) {} 37 | func (m *MockDaemon) ImageLoad(context.Context, io.Reader, ...client.ImageLoadOption) (image.LoadResponse, error) { 38 | return image.LoadResponse{ 39 | Body: io.NopCloser(strings.NewReader("Loaded")), 40 | }, nil 41 | } 42 | 43 | func (m *MockDaemon) ImageTag(_ context.Context, _ string, tag string) error { 44 | if m.Tags == nil { 45 | m.Tags = []string{} 46 | } 47 | m.Tags = append(m.Tags, tag) 48 | return nil 49 | } 50 | 51 | func (m *MockDaemon) ImageInspectWithRaw(_ context.Context, _ string) (image.InspectResponse, []byte, error) { 52 | return m.inspectResp, m.inspectBody, m.inspectErr 53 | } 54 | -------------------------------------------------------------------------------- /pkg/internal/testing/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package testing holds a variety test doubles to help with testing 16 | package testing 17 | -------------------------------------------------------------------------------- /pkg/internal/testing/fixed.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package testing 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "strings" 22 | 23 | "github.com/google/go-containerregistry/pkg/name" 24 | v1 "github.com/google/go-containerregistry/pkg/v1" 25 | "github.com/google/ko/pkg/build" 26 | "github.com/google/ko/pkg/publish" 27 | ) 28 | 29 | type fixedBuild struct { 30 | entries map[string]build.Result 31 | } 32 | 33 | // NewFixedBuild returns a build.Interface implementation that simply resolves 34 | // particular references to fixed v1.Image objects 35 | func NewFixedBuild(entries map[string]build.Result) build.Interface { 36 | return &fixedBuild{entries} 37 | } 38 | 39 | // QualifyImport implements build.Interface 40 | func (f *fixedBuild) QualifyImport(ip string) (string, error) { 41 | return ip, nil 42 | } 43 | 44 | // IsSupportedReference implements build.Interface 45 | func (f *fixedBuild) IsSupportedReference(s string) error { 46 | s = strings.TrimPrefix(s, build.StrictScheme) 47 | if _, ok := f.entries[s]; !ok { 48 | return errors.New("importpath is not supported") 49 | } 50 | return nil 51 | } 52 | 53 | // Build implements build.Interface 54 | func (f *fixedBuild) Build(_ context.Context, s string) (build.Result, error) { 55 | s = strings.TrimPrefix(s, build.StrictScheme) 56 | if img, ok := f.entries[s]; ok { 57 | return img, nil 58 | } 59 | return nil, fmt.Errorf("unsupported reference: %q", s) 60 | } 61 | 62 | type fixedPublish struct { 63 | base name.Repository 64 | entries map[string]v1.Hash 65 | } 66 | 67 | // NewFixedPublish returns a publish.Interface implementation that simply 68 | // resolves particular references to fixed name.Digest references. 69 | func NewFixedPublish(base name.Repository, entries map[string]v1.Hash) publish.Interface { 70 | return &fixedPublish{base, entries} 71 | } 72 | 73 | // Publish implements publish.Interface 74 | func (f *fixedPublish) Publish(_ context.Context, _ build.Result, s string) (name.Reference, error) { 75 | s = strings.TrimPrefix(s, build.StrictScheme) 76 | h, ok := f.entries[s] 77 | if !ok { 78 | return nil, fmt.Errorf("unsupported importpath: %q", s) 79 | } 80 | d, err := name.NewDigest(fmt.Sprintf("%s/%s@%s", f.base, s, h)) 81 | if err != nil { 82 | return nil, err 83 | } 84 | return &d, nil 85 | } 86 | 87 | func (f *fixedPublish) Close() error { 88 | return nil 89 | } 90 | 91 | func ComputeDigest(base name.Repository, ref string, h v1.Hash) string { 92 | d, err := name.NewDigest(fmt.Sprintf("%s/%s@%s", base, ref, h)) 93 | if err != nil { 94 | panic(err) 95 | } 96 | return d.String() 97 | } 98 | -------------------------------------------------------------------------------- /pkg/internal/testing/fixed_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package testing 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | 21 | "github.com/google/go-containerregistry/pkg/name" 22 | v1 "github.com/google/go-containerregistry/pkg/v1" 23 | "github.com/google/go-containerregistry/pkg/v1/random" 24 | "github.com/google/ko/pkg/build" 25 | ) 26 | 27 | func TestFixedPublish(t *testing.T) { 28 | hex1 := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" 29 | hex2 := "baadf00dbaadf00dbaadf00dbaadf00dbaadf00dbaadf00dbaadf00dbaadf00d" 30 | fixedBaseRepo, _ := name.NewRepository("gcr.io/asdf") 31 | f := NewFixedPublish(fixedBaseRepo, map[string]v1.Hash{ 32 | "foo": { 33 | Algorithm: "sha256", 34 | Hex: hex1, 35 | }, 36 | "bar": { 37 | Algorithm: "sha256", 38 | Hex: hex2, 39 | }, 40 | }) 41 | 42 | fooDigest, err := f.Publish(context.Background(), nil, "foo") 43 | if err != nil { 44 | t.Errorf("Publish(foo) = %v", err) 45 | } 46 | if got, want := fooDigest.String(), "gcr.io/asdf/foo@sha256:"+hex1; got != want { 47 | t.Errorf("Publish(foo) = %q, want %q", got, want) 48 | } 49 | 50 | barDigest, err := f.Publish(context.Background(), nil, "bar") 51 | if err != nil { 52 | t.Errorf("Publish(bar) = %v", err) 53 | } 54 | if got, want := barDigest.String(), "gcr.io/asdf/bar@sha256:"+hex2; got != want { 55 | t.Errorf("Publish(bar) = %q, want %q", got, want) 56 | } 57 | 58 | d, err := f.Publish(context.Background(), nil, "baz") 59 | if err == nil { 60 | t.Errorf("Publish(baz) = %v, want error", d) 61 | } 62 | } 63 | 64 | func TestFixedBuild(t *testing.T) { 65 | testImage, _ := random.Image(1024, 5) 66 | f := NewFixedBuild(map[string]build.Result{ 67 | "asdf": testImage, 68 | }) 69 | 70 | if got := f.IsSupportedReference("asdf"); got != nil { 71 | t.Errorf("IsSupportedReference(asdf) = (%v), want nil", got) 72 | } 73 | if got, err := f.Build(context.Background(), "asdf"); err != nil { 74 | t.Errorf("Build(asdf) = %v, want %v", err, testImage) 75 | } else if got != testImage { 76 | t.Errorf("Build(asdf) = %v, want %v", got, testImage) 77 | } 78 | 79 | if got := f.IsSupportedReference("blah"); got == nil { 80 | t.Error("IsSupportedReference(blah) = nil, want error") 81 | } 82 | if got, err := f.Build(context.Background(), "blah"); err == nil { 83 | t.Errorf("Build(blah) = %v, want error", got) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /pkg/publish/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package publish defines methods for publishing a v1.Image reference and 16 | // returning the published digest for embedding back into a Kubernetes yaml. 17 | package publish 18 | -------------------------------------------------------------------------------- /pkg/publish/future.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package publish 16 | 17 | import ( 18 | "sync" 19 | 20 | "github.com/google/go-containerregistry/pkg/name" 21 | ) 22 | 23 | func newFuture(work func() (name.Reference, error)) *future { 24 | // Create a channel on which to send the result. 25 | ch := make(chan *result) 26 | // Initiate the actual work, sending its result 27 | // along the above channel. 28 | go func() { 29 | ref, err := work() 30 | ch <- &result{ref: ref, err: err} 31 | }() 32 | // Return a future for the above work. Callers should 33 | // call .Get() on this result (as many times as needed). 34 | // One of these calls will receive the result, store it, 35 | // and close the channel so that the rest of the callers 36 | // can consume it. 37 | return &future{ 38 | promise: ch, 39 | } 40 | } 41 | 42 | type result struct { 43 | ref name.Reference 44 | err error 45 | } 46 | 47 | type future struct { 48 | m sync.RWMutex 49 | 50 | result *result 51 | promise chan *result 52 | } 53 | 54 | // Get blocks on the result of the future. 55 | func (f *future) Get() (name.Reference, error) { 56 | // Block on the promise of a result until we get one. 57 | result, ok := <-f.promise 58 | if ok { 59 | func() { 60 | f.m.Lock() 61 | defer f.m.Unlock() 62 | // If we got the result, then store it so that 63 | // others may access it. 64 | f.result = result 65 | // Close the promise channel so that others 66 | // are signaled that the result is available. 67 | close(f.promise) 68 | }() 69 | } 70 | 71 | f.m.RLock() 72 | defer f.m.RUnlock() 73 | 74 | return f.result.ref, f.result.err 75 | } 76 | -------------------------------------------------------------------------------- /pkg/publish/future_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package publish 16 | 17 | import ( 18 | "fmt" 19 | "testing" 20 | 21 | "github.com/google/go-containerregistry/pkg/name" 22 | "github.com/google/go-containerregistry/pkg/v1/random" 23 | ) 24 | 25 | func makeRef() (name.Reference, error) { 26 | img, err := random.Image(256, 8) 27 | if err != nil { 28 | return nil, err 29 | } 30 | d, err := img.Digest() 31 | if err != nil { 32 | return nil, err 33 | } 34 | return name.NewDigest(fmt.Sprintf("gcr.io/foo/bar@%s", d)) 35 | } 36 | 37 | func TestSameFutureSameReference(t *testing.T) { 38 | f := newFuture(makeRef) 39 | 40 | ref1, err := f.Get() 41 | if err != nil { 42 | t.Errorf("Get() = %v", err) 43 | } 44 | d1 := ref1.String() 45 | 46 | ref2, err := f.Get() 47 | if err != nil { 48 | t.Errorf("Get() = %v", err) 49 | } 50 | d2 := ref2.String() 51 | 52 | if d1 != d2 { 53 | t.Errorf("Got different digests %s and %s", d1, d2) 54 | } 55 | } 56 | 57 | func TestDiffFutureDiffReference(t *testing.T) { 58 | f1 := newFuture(makeRef) 59 | f2 := newFuture(makeRef) 60 | 61 | ref1, err := f1.Get() 62 | if err != nil { 63 | t.Errorf("Get() = %v", err) 64 | } 65 | d1 := ref1.String() 66 | 67 | ref2, err := f2.Get() 68 | if err != nil { 69 | t.Errorf("Get() = %v", err) 70 | } 71 | d2 := ref2.String() 72 | 73 | if d1 == d2 { 74 | t.Errorf("Got same digest %s, wanted different", d1) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /pkg/publish/kind/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package kind defines methods for publishing images into kind nodes. 16 | package kind 17 | -------------------------------------------------------------------------------- /pkg/publish/layout.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package publish 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "log" 21 | 22 | "github.com/google/go-containerregistry/pkg/name" 23 | v1 "github.com/google/go-containerregistry/pkg/v1" 24 | "github.com/google/go-containerregistry/pkg/v1/empty" 25 | "github.com/google/go-containerregistry/pkg/v1/layout" 26 | "github.com/google/go-containerregistry/pkg/v1/types" 27 | "github.com/google/ko/pkg/build" 28 | ) 29 | 30 | type LayoutPublisher struct { 31 | p string 32 | } 33 | 34 | // NewLayout returns a new publish.Interface that saves images to an OCI Image Layout. 35 | func NewLayout(p string) Interface { 36 | return &LayoutPublisher{p} 37 | } 38 | 39 | func (l *LayoutPublisher) writeResult(br build.Result) (layout.Path, error) { 40 | p, err := layout.FromPath(l.p) 41 | if err != nil { 42 | p, err = layout.Write(l.p, empty.Index) 43 | if err != nil { 44 | return "", err 45 | } 46 | } 47 | 48 | mt, err := br.MediaType() 49 | if err != nil { 50 | return "", err 51 | } 52 | 53 | switch mt { 54 | case types.OCIImageIndex, types.DockerManifestList: 55 | idx, ok := br.(v1.ImageIndex) 56 | if !ok { 57 | return "", fmt.Errorf("failed to interpret result as index: %v", br) 58 | } 59 | if err := p.AppendIndex(idx); err != nil { 60 | return "", err 61 | } 62 | return p, nil 63 | case types.OCIManifestSchema1, types.DockerManifestSchema2: 64 | img, ok := br.(v1.Image) 65 | if !ok { 66 | return "", fmt.Errorf("failed to interpret result as image: %v", br) 67 | } 68 | if err := p.AppendImage(img); err != nil { 69 | return "", err 70 | } 71 | return p, nil 72 | default: 73 | return "", fmt.Errorf("result image media type: %s", mt) 74 | } 75 | } 76 | 77 | // Publish implements publish.Interface. 78 | func (l *LayoutPublisher) Publish(_ context.Context, br build.Result, s string) (name.Reference, error) { 79 | log.Printf("Saving %v", s) 80 | p, err := l.writeResult(br) 81 | if err != nil { 82 | return nil, err 83 | } 84 | log.Printf("Saved %v", s) 85 | 86 | h, err := br.Digest() 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | dig, err := name.NewDigest(fmt.Sprintf("%s@%s", p, h)) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | return dig, nil 97 | } 98 | 99 | func (l *LayoutPublisher) Close() error { 100 | return nil 101 | } 102 | -------------------------------------------------------------------------------- /pkg/publish/layout_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package publish 16 | 17 | import ( 18 | "context" 19 | "os" 20 | "strings" 21 | "testing" 22 | 23 | "github.com/google/go-containerregistry/pkg/v1/random" 24 | ) 25 | 26 | func TestLayout(t *testing.T) { 27 | img, err := random.Image(1024, 1) 28 | if err != nil { 29 | t.Fatalf("random.Image() = %v", err) 30 | } 31 | importpath := "github.com/Google/go-containerregistry/cmd/crane" 32 | 33 | tmp, err := os.MkdirTemp("/tmp", "ko") 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | defer os.RemoveAll(tmp) 38 | 39 | lp := NewLayout(tmp) 40 | if d, err := lp.Publish(context.Background(), img, importpath); err != nil { 41 | t.Errorf("Publish() = %v", err) 42 | } else if !strings.HasPrefix(d.String(), tmp) { 43 | t.Errorf("Publish() = %v, wanted prefix %v", d, tmp) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pkg/publish/multi.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package publish 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | 21 | "github.com/google/go-containerregistry/pkg/name" 22 | "github.com/google/ko/pkg/build" 23 | ) 24 | 25 | // MultiPublisher creates a publisher that publishes to all 26 | // the provided publishers, similar to the Unix tee(1) command. 27 | // 28 | // When calling Publish, the name.Reference returned will be the return value 29 | // of the last publisher passed to MultiPublisher (last one wins). 30 | func MultiPublisher(publishers ...Interface) Interface { 31 | return &multiPublisher{publishers} 32 | } 33 | 34 | type multiPublisher struct { 35 | publishers []Interface 36 | } 37 | 38 | // Publish implements publish.Interface. 39 | func (p *multiPublisher) Publish(ctx context.Context, br build.Result, s string) (ref name.Reference, err error) { 40 | if len(p.publishers) == 0 { 41 | return nil, errors.New("MultiPublisher configured with zero publishers") 42 | } 43 | 44 | for _, pub := range p.publishers { 45 | ref, err = pub.Publish(ctx, br, s) 46 | if err != nil { 47 | return 48 | } 49 | } 50 | return 51 | } 52 | 53 | func (p *multiPublisher) Close() (err error) { 54 | for _, pub := range p.publishers { 55 | if perr := pub.Close(); perr != nil { 56 | err = perr 57 | } 58 | } 59 | return 60 | } 61 | -------------------------------------------------------------------------------- /pkg/publish/multi_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package publish_test 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "os" 21 | "testing" 22 | 23 | "github.com/google/go-containerregistry/pkg/v1/random" 24 | "github.com/google/ko/pkg/publish" 25 | ) 26 | 27 | func TestMulti(t *testing.T) { 28 | img, err := random.Image(1024, 1) 29 | if err != nil { 30 | t.Fatalf("random.Image() = %v", err) 31 | } 32 | base := "blah" 33 | repoName := fmt.Sprintf("%s/%s", "example.com", base) 34 | importpath := "github.com/Google/go-containerregistry/cmd/crane" 35 | 36 | fp, err := os.CreateTemp("", "") 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | defer fp.Close() 41 | defer os.Remove(fp.Name()) 42 | 43 | tp := publish.NewTarball(fp.Name(), repoName, md5Hash, []string{}) 44 | 45 | tmp, err := os.MkdirTemp("/tmp", "ko") 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | defer os.RemoveAll(tmp) 50 | 51 | lp := publish.NewLayout(tmp) 52 | 53 | p := publish.MultiPublisher(lp, tp) 54 | if _, err := p.Publish(context.Background(), img, importpath); err != nil { 55 | t.Errorf("Publish() = %v", err) 56 | } 57 | 58 | if err := p.Close(); err != nil { 59 | t.Errorf("Close() = %v", err) 60 | } 61 | } 62 | 63 | func TestMulti_Zero(t *testing.T) { 64 | img, err := random.Image(1024, 1) 65 | if err != nil { 66 | t.Fatalf("random.Image() = %v", err) 67 | } 68 | 69 | p := publish.MultiPublisher() // No publishers. 70 | if _, err := p.Publish(context.Background(), img, "foo"); err == nil { 71 | t.Errorf("Publish() got nil error") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pkg/publish/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package publish 16 | 17 | import ( 18 | "crypto/tls" 19 | "net/http" 20 | 21 | "github.com/google/go-containerregistry/pkg/authn" 22 | ) 23 | 24 | type staticKeychain struct { 25 | auth authn.Authenticator 26 | } 27 | 28 | func (s staticKeychain) Resolve(authn.Resource) (authn.Authenticator, error) { 29 | return s.auth, nil 30 | } 31 | 32 | // WithTransport is a functional option for overriding the default transport 33 | // on a default publisher. 34 | func WithTransport(t http.RoundTripper) Option { 35 | return func(i *defaultOpener) error { 36 | i.t = t 37 | return nil 38 | } 39 | } 40 | 41 | // WithUserAgent is a functional option for overriding the User-Agent 42 | // on a default publisher. 43 | func WithUserAgent(ua string) Option { 44 | return func(i *defaultOpener) error { 45 | i.userAgent = ua 46 | return nil 47 | } 48 | } 49 | 50 | // WithAuth is a functional option for overriding the default authenticator 51 | // on a default publisher. 52 | func WithAuth(a authn.Authenticator) Option { 53 | return func(i *defaultOpener) error { 54 | i.keychain = staticKeychain{a} 55 | return nil 56 | } 57 | } 58 | 59 | // WithAuthFromKeychain is a functional option for overriding the default 60 | // authenticator on a default publisher using an authn.Keychain 61 | func WithAuthFromKeychain(keys authn.Keychain) Option { 62 | return func(i *defaultOpener) error { 63 | i.keychain = keys 64 | return nil 65 | } 66 | } 67 | 68 | // WithNamer is a functional option for overriding the image naming behavior 69 | // in our default publisher. 70 | func WithNamer(n Namer) Option { 71 | return func(i *defaultOpener) error { 72 | i.namer = n 73 | return nil 74 | } 75 | } 76 | 77 | // WithTags is a functional option for overriding the image tags 78 | func WithTags(tags []string) Option { 79 | return func(i *defaultOpener) error { 80 | i.tags = tags 81 | return nil 82 | } 83 | } 84 | 85 | // WithTagOnly is a functional option for resolving images into tag-only references 86 | func WithTagOnly(tagOnly bool) Option { 87 | return func(i *defaultOpener) error { 88 | i.tagOnly = tagOnly 89 | return nil 90 | } 91 | } 92 | 93 | func Insecure(b bool) Option { 94 | return func(i *defaultOpener) error { 95 | i.insecure = b 96 | t, ok := i.t.(*http.Transport) 97 | if !ok { 98 | return nil 99 | } 100 | t = t.Clone() 101 | if t.TLSClientConfig == nil { 102 | t.TLSClientConfig = &tls.Config{} //nolint: gosec 103 | } 104 | t.TLSClientConfig.InsecureSkipVerify = b //nolint: gosec 105 | i.t = t 106 | 107 | return nil 108 | } 109 | } 110 | 111 | // WithJobs limits the number of concurrent pushes. 112 | func WithJobs(jobs int) Option { 113 | return func(i *defaultOpener) error { 114 | i.jobs = jobs 115 | return nil 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /pkg/publish/publish.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package publish 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/google/go-containerregistry/pkg/name" 21 | "github.com/google/ko/pkg/build" 22 | ) 23 | 24 | // Interface abstracts different methods for publishing images. 25 | type Interface interface { 26 | // Publish uploads the given build.Result to a registry incorporating the 27 | // provided string into the image's repository name. Returns the digest 28 | // of the published image. 29 | Publish(context.Context, build.Result, string) (name.Reference, error) 30 | 31 | // Close exists for the tarball implementation so we can 32 | // do the whole thing in one write. 33 | Close() error 34 | } 35 | -------------------------------------------------------------------------------- /pkg/publish/recorder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package publish 16 | 17 | import ( 18 | "context" 19 | "io" 20 | "os" 21 | "strings" 22 | 23 | "github.com/google/go-containerregistry/pkg/name" 24 | v1 "github.com/google/go-containerregistry/pkg/v1" 25 | "github.com/google/ko/pkg/build" 26 | "github.com/sigstore/cosign/v2/pkg/oci" 27 | "github.com/sigstore/cosign/v2/pkg/oci/walk" 28 | ) 29 | 30 | // recorder wraps a publisher implementation in a layer that recordes the published 31 | // references to a file. 32 | type recorder struct { 33 | inner Interface 34 | fileName string 35 | wc io.Writer 36 | } 37 | 38 | // recorder implements Interface 39 | var _ Interface = (*recorder)(nil) 40 | 41 | // NewRecorder wraps the provided publish.Interface in an implementation that 42 | // records publish results to a file. 43 | func NewRecorder(inner Interface, name string) (Interface, error) { 44 | return &recorder{ 45 | inner: inner, 46 | fileName: name, 47 | }, nil 48 | } 49 | 50 | // Publish implements Interface 51 | func (r *recorder) Publish(ctx context.Context, br build.Result, ref string) (name.Reference, error) { 52 | result, err := r.inner.Publish(ctx, br, ref) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | references := make([]string, 0, 20 /* just try to avoid resizing*/) 58 | switch t := br.(type) { 59 | case oci.SignedImageIndex: 60 | if err := walk.SignedEntity(ctx, t, func(_ context.Context, se oci.SignedEntity) error { 61 | // Both of the SignedEntity types implement Digest() 62 | h, err := se.(interface{ Digest() (v1.Hash, error) }).Digest() 63 | if err != nil { 64 | return err 65 | } 66 | references = append(references, result.Context().Digest(h.String()).String()) 67 | return nil 68 | }); err != nil { 69 | return nil, err 70 | } 71 | default: 72 | references = append(references, result.String()) 73 | } 74 | 75 | if r.wc == nil { 76 | f, err := os.OpenFile(r.fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) 77 | if err != nil { 78 | return nil, err 79 | } 80 | r.wc = f 81 | } 82 | 83 | if _, err := r.wc.Write([]byte(strings.Join(references, "\n") + "\n")); err != nil { 84 | return nil, err 85 | } 86 | return result, nil 87 | } 88 | 89 | // Close implements Interface 90 | func (r *recorder) Close() error { 91 | if err := r.inner.Close(); err != nil { 92 | return err 93 | } 94 | if c, ok := r.wc.(io.Closer); ok { 95 | return c.Close() 96 | } 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /pkg/publish/recorder_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package publish 16 | 17 | import ( 18 | "context" 19 | "os" 20 | "path" 21 | "strings" 22 | "testing" 23 | 24 | "github.com/google/go-containerregistry/pkg/name" 25 | "github.com/google/go-containerregistry/pkg/v1/random" 26 | "github.com/google/ko/pkg/build" 27 | "github.com/sigstore/cosign/v2/pkg/oci/signed" 28 | ) 29 | 30 | type cbPublish struct { 31 | cb func(context.Context, build.Result, string) (name.Reference, error) 32 | } 33 | 34 | var _ Interface = (*cbPublish)(nil) 35 | 36 | func (sp *cbPublish) Publish(ctx context.Context, br build.Result, ref string) (name.Reference, error) { 37 | return sp.cb(ctx, br, ref) 38 | } 39 | 40 | func (sp *cbPublish) Close() error { 41 | return nil 42 | } 43 | 44 | func TestRecorder(t *testing.T) { 45 | repo := name.MustParseReference("docker.io/ubuntu:latest") 46 | inner := &cbPublish{cb: func(_ context.Context, b build.Result, _ string) (name.Reference, error) { 47 | h, err := b.Digest() 48 | if err != nil { 49 | return nil, err 50 | } 51 | return repo.Context().Digest(h.String()), nil 52 | }} 53 | 54 | dir := t.TempDir() 55 | file := path.Join(dir, "testfile") 56 | 57 | recorder, err := NewRecorder(inner, file) 58 | if err != nil { 59 | t.Fatalf("NewRecorder() = %v", err) 60 | } 61 | 62 | img, err := random.Image(3, 3) 63 | if err != nil { 64 | t.Fatalf("random.Image() = %v", err) 65 | } 66 | si := signed.Image(img) 67 | 68 | index, err := random.Index(3, 3, 2) 69 | if err != nil { 70 | t.Fatalf("random.Image() = %v", err) 71 | } 72 | sii := signed.ImageIndex(index) 73 | 74 | if _, err := recorder.Publish(context.Background(), si, ""); err != nil { 75 | t.Errorf("recorder.Publish() = %v", err) 76 | } 77 | if _, err := recorder.Publish(context.Background(), si, ""); err != nil { 78 | t.Errorf("recorder.Publish() = %v", err) 79 | } 80 | if _, err := recorder.Publish(context.Background(), sii, ""); err != nil { 81 | t.Errorf("recorder.Publish() = %v", err) 82 | } 83 | if err := recorder.Close(); err != nil { 84 | t.Errorf("recorder.Close() = %v", err) 85 | } 86 | 87 | buf, err := os.ReadFile(file) 88 | if err != nil { 89 | t.Fatalf("os.ReadFile() = %v", err) 90 | } 91 | refs := strings.Split(strings.TrimSpace(string(buf)), "\n") 92 | 93 | if want, got := len(refs), 5; got != want { 94 | t.Errorf("len(refs) = %d, wanted %d", got, want) 95 | } 96 | 97 | for _, s := range refs { 98 | ref, err := name.ParseReference(s) 99 | if err != nil { 100 | t.Fatalf("name.ParseReference() = %v", err) 101 | } 102 | // Don't compare the digests, they are random. 103 | if want, got := repo.Context().String(), ref.Context().String(); want != got { 104 | t.Errorf("reference repo = %v, wanted %v", got, want) 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /pkg/publish/shared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package publish 16 | 17 | import ( 18 | "context" 19 | "sync" 20 | 21 | "github.com/google/go-containerregistry/pkg/name" 22 | "github.com/google/ko/pkg/build" 23 | ) 24 | 25 | // caching wraps a publisher implementation in a layer that shares publish results 26 | // for the same inputs using a simple "future" implementation. 27 | type caching struct { 28 | inner Interface 29 | 30 | m sync.Mutex 31 | results map[string]*entry 32 | } 33 | 34 | // entry holds the last image published and the result of publishing it for a 35 | // particular reference. 36 | type entry struct { 37 | br build.Result 38 | f *future 39 | } 40 | 41 | // caching implements Interface 42 | var _ Interface = (*caching)(nil) 43 | 44 | // NewCaching wraps the provided publish.Interface in an implementation that 45 | // shares publish results for a given path until the passed image object changes. 46 | func NewCaching(inner Interface) (Interface, error) { 47 | return &caching{ 48 | inner: inner, 49 | results: make(map[string]*entry), 50 | }, nil 51 | } 52 | 53 | // Publish implements Interface 54 | func (c *caching) Publish(ctx context.Context, br build.Result, ref string) (name.Reference, error) { 55 | f := func() *future { 56 | // Lock the map of futures. 57 | c.m.Lock() 58 | defer c.m.Unlock() 59 | 60 | // If a future for "ref" exists, then return it. 61 | ent, ok := c.results[ref] 62 | if ok { 63 | // If the image matches, then return the same future. 64 | if ent.br == br { 65 | return ent.f 66 | } 67 | } 68 | // Otherwise create and record a future for publishing "br" to "ref". 69 | f := newFuture(func() (name.Reference, error) { 70 | return c.inner.Publish(ctx, br, ref) 71 | }) 72 | c.results[ref] = &entry{br: br, f: f} 73 | return f 74 | }() 75 | 76 | return f.Get() 77 | } 78 | 79 | func (c *caching) Close() error { 80 | return c.inner.Close() 81 | } 82 | -------------------------------------------------------------------------------- /pkg/publish/shared_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package publish 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | "time" 21 | 22 | "github.com/google/go-containerregistry/pkg/name" 23 | "github.com/google/go-containerregistry/pkg/v1/random" 24 | "github.com/google/ko/pkg/build" 25 | ) 26 | 27 | type slowpublish struct { 28 | sleep time.Duration 29 | } 30 | 31 | // slowpublish implements Interface 32 | var _ Interface = (*slowpublish)(nil) 33 | 34 | func (sp *slowpublish) Publish(context.Context, build.Result, string) (name.Reference, error) { 35 | time.Sleep(sp.sleep) 36 | return makeRef() 37 | } 38 | 39 | func (sp *slowpublish) Close() error { 40 | return nil 41 | } 42 | 43 | func TestCaching(t *testing.T) { 44 | duration := 100 * time.Millisecond 45 | ref := "foo" 46 | 47 | sp := &slowpublish{duration} 48 | cb, _ := NewCaching(sp) 49 | 50 | previousDigest := "not-a-digest" 51 | // Each iteration, we test that the first publish is slow and subsequent 52 | // publishs are fast and return the same reference. For each of these 53 | // iterations we use a new random image, which should invalidate the 54 | // cached reference from previous iterations. 55 | for idx := 0; idx < 3; idx++ { 56 | img, _ := random.Index(256, 8, 1) 57 | 58 | start := time.Now() 59 | ref1, err := cb.Publish(context.Background(), img, ref) 60 | if err != nil { 61 | t.Errorf("Publish() = %v", err) 62 | } 63 | end := time.Now() 64 | 65 | elapsed := end.Sub(start) 66 | if elapsed < duration { 67 | t.Errorf("Elapsed time %v, wanted >= %s", elapsed, duration) 68 | } 69 | d1 := ref1.String() 70 | 71 | if d1 == previousDigest { 72 | t.Errorf("Got same digest as previous iteration, wanted different: %v", d1) 73 | } 74 | previousDigest = d1 75 | 76 | start = time.Now() 77 | ref2, err := cb.Publish(context.Background(), img, ref) 78 | if err != nil { 79 | t.Errorf("Publish() = %v", err) 80 | } 81 | end = time.Now() 82 | 83 | elapsed = end.Sub(start) 84 | if elapsed >= duration { 85 | t.Errorf("Elapsed time %v, wanted < %s", elapsed, duration) 86 | } 87 | d2 := ref2.String() 88 | 89 | if d1 != d2 { 90 | t.Error("Got different references, wanted same") 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /pkg/publish/tarball.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package publish 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "log" 21 | "strings" 22 | 23 | "github.com/google/go-containerregistry/pkg/name" 24 | v1 "github.com/google/go-containerregistry/pkg/v1" 25 | "github.com/google/go-containerregistry/pkg/v1/tarball" 26 | "github.com/google/ko/pkg/build" 27 | ) 28 | 29 | type tar struct { 30 | file string 31 | base string 32 | namer Namer 33 | tags []string 34 | refs map[name.Reference]v1.Image 35 | } 36 | 37 | // NewTarball returns a new publish.Interface that saves images to a tarball. 38 | func NewTarball(file, base string, namer Namer, tags []string) Interface { 39 | return &tar{ 40 | file: file, 41 | base: base, 42 | namer: namer, 43 | tags: tags, 44 | refs: make(map[name.Reference]v1.Image), 45 | } 46 | } 47 | 48 | // Publish implements publish.Interface. 49 | func (t *tar) Publish(_ context.Context, br build.Result, s string) (name.Reference, error) { 50 | s = strings.TrimPrefix(s, build.StrictScheme) 51 | // https://github.com/google/go-containerregistry/issues/212 52 | s = strings.ToLower(s) 53 | 54 | // There's no way to write an index to a tarball, so attempt to downcast it to an image. 55 | img, ok := br.(v1.Image) 56 | if !ok { 57 | return nil, fmt.Errorf("failed to interpret %s result as image: %v", s, br) 58 | } 59 | 60 | for _, tagName := range t.tags { 61 | tag, err := name.ParseReference(fmt.Sprintf("%s:%s", t.namer(t.base, s), tagName)) 62 | if err != nil { 63 | return nil, err 64 | } 65 | t.refs[tag] = img 66 | } 67 | 68 | h, err := img.Digest() 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | if len(t.tags) == 0 { 74 | ref, err := name.ParseReference(fmt.Sprintf("%s@%s", t.namer(t.base, s), h)) 75 | if err != nil { 76 | return nil, err 77 | } 78 | t.refs[ref] = img 79 | } 80 | 81 | ref := fmt.Sprintf("%s@%s", t.namer(t.base, s), h) 82 | if len(t.tags) == 1 && t.tags[0] != latestTag { 83 | // If a single tag is explicitly set (not latest), then this 84 | // is probably a release, so include the tag in the reference. 85 | ref = fmt.Sprintf("%s:%s@%s", t.namer(t.base, s), t.tags[0], h) 86 | } 87 | dig, err := name.NewDigest(ref) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | return &dig, nil 93 | } 94 | 95 | func (t *tar) Close() error { 96 | log.Printf("Saving %v", t.file) 97 | if err := tarball.MultiRefWriteToFile(t.file, t.refs); err != nil { 98 | // Bad practice, but we log this here because right now we just defer the Close. 99 | log.Printf("failed to save %q: %v", t.file, err) 100 | return err 101 | } 102 | log.Printf("Saved %v", t.file) 103 | return nil 104 | } 105 | -------------------------------------------------------------------------------- /pkg/publish/tarball_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package publish_test 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "os" 21 | "strings" 22 | "testing" 23 | 24 | "github.com/google/go-containerregistry/pkg/name" 25 | "github.com/google/go-containerregistry/pkg/v1/random" 26 | "github.com/google/ko/pkg/publish" 27 | ) 28 | 29 | func TestTarball(t *testing.T) { 30 | img, err := random.Image(1024, 1) 31 | if err != nil { 32 | t.Fatalf("random.Image() = %v", err) 33 | } 34 | base := "blah" 35 | importpath := "github.com/Google/go-containerregistry/cmd/crane" 36 | 37 | fp, err := os.CreateTemp("", "") 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | defer fp.Close() 42 | defer os.Remove(fp.Name()) 43 | 44 | expectedRepo := md5Hash(base, strings.ToLower(importpath)) 45 | 46 | tag, err := name.NewTag(fmt.Sprintf("%s/%s:latest", "example.com", expectedRepo)) 47 | if err != nil { 48 | t.Fatalf("NewTag() = %v", err) 49 | } 50 | 51 | repoName := fmt.Sprintf("%s/%s", "example.com", base) 52 | tagss := [][]string{{ 53 | // no tags 54 | }, { 55 | // one tag 56 | "v0.1.0", 57 | }, { 58 | // multiple tags 59 | "latest", 60 | "debug", 61 | }} 62 | for _, tags := range tagss { 63 | tp := publish.NewTarball(fp.Name(), repoName, md5Hash, tags) 64 | if d, err := tp.Publish(context.Background(), img, importpath); err != nil { 65 | t.Errorf("Publish() = %v", err) 66 | } else if !strings.HasPrefix(d.String(), tag.Repository.String()) { 67 | t.Errorf("Publish() = %v, wanted prefix %v", d, tag.Repository) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /pkg/resolve/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package resolve defines logic for resolving K8s yaml inputs to ko. 16 | package resolve 17 | -------------------------------------------------------------------------------- /pkg/resolve/resolve.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package resolve 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "strings" 21 | "sync" 22 | 23 | "github.com/dprotaso/go-yit" 24 | "github.com/google/ko/pkg/build" 25 | "github.com/google/ko/pkg/publish" 26 | "golang.org/x/sync/errgroup" 27 | "gopkg.in/yaml.v3" 28 | ) 29 | 30 | // ImageReferences resolves supported references to images within the input yaml 31 | // to published image digests. 32 | // 33 | // If a reference can be built and pushed, its yaml.Node will be mutated. 34 | func ImageReferences(ctx context.Context, docs []*yaml.Node, builder build.Interface, publisher publish.Interface) error { 35 | // First, walk the input objects and collect a list of supported references 36 | refs := make(map[string][]*yaml.Node) 37 | 38 | for _, doc := range docs { 39 | it := refsFromDoc(doc) 40 | 41 | for node, ok := it(); ok; node, ok = it() { 42 | ref := strings.TrimSpace(node.Value) 43 | 44 | if err := builder.IsSupportedReference(ref); err != nil { 45 | return fmt.Errorf("found strict reference but %s is not a valid import path: %w", ref, err) 46 | } 47 | 48 | refs[ref] = append(refs[ref], node) 49 | } 50 | } 51 | 52 | // Next, perform parallel builds for each of the supported references. 53 | var sm sync.Map 54 | var errg errgroup.Group 55 | for ref := range refs { 56 | ref := ref 57 | errg.Go(func() error { 58 | img, err := builder.Build(ctx, ref) 59 | if err != nil { 60 | return err 61 | } 62 | digest, err := publisher.Publish(ctx, img, ref) 63 | if err != nil { 64 | return err 65 | } 66 | sm.Store(ref, digest.String()) 67 | return nil 68 | }) 69 | } 70 | if err := errg.Wait(); err != nil { 71 | return err 72 | } 73 | 74 | // Walk the tags and update them with their digest. 75 | for ref, nodes := range refs { 76 | digest, ok := sm.Load(ref) 77 | 78 | if !ok { 79 | return fmt.Errorf("resolved reference to %q not found", ref) 80 | } 81 | 82 | for _, node := range nodes { 83 | node.Value = digest.(string) 84 | } 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func refsFromDoc(doc *yaml.Node) yit.Iterator { 91 | it := yit.FromNode(doc). 92 | RecurseNodes(). 93 | Filter(yit.StringValue) 94 | 95 | return it.Filter(yit.WithPrefix(build.StrictScheme)) 96 | } 97 | -------------------------------------------------------------------------------- /test/build-configs/.ko.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 ko Build Authors All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | builds: 16 | - id: foo-app 17 | dir: ./foo 18 | main: ./cmd 19 | flags: -v -v # build.Config parser must handle shorthand syntax 20 | - id: bar-app 21 | dir: ./bar 22 | main: ./cmd 23 | - id: toolexec 24 | dir: ./toolexec 25 | main: ./cmd 26 | flags: 27 | - -toolexec 28 | - go 29 | - id: caps-app 30 | dir: ./caps 31 | main: ./cmd 32 | -------------------------------------------------------------------------------- /test/build-configs/bar/cmd/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import "fmt" 18 | 19 | func main() { 20 | fmt.Println("bar") 21 | } 22 | -------------------------------------------------------------------------------- /test/build-configs/bar/go.mod: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module example.com/bar 16 | 17 | go 1.16 18 | -------------------------------------------------------------------------------- /test/build-configs/caps.ko.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 ko Build Authors All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | builds: 16 | - id: caps-app-with-caps 17 | dir: ./caps 18 | main: ./cmd 19 | linux_capabilities: net_bind_service chown 20 | -------------------------------------------------------------------------------- /test/build-configs/caps/cmd/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "io/ioutil" 20 | "os" 21 | "strconv" 22 | "strings" 23 | ) 24 | 25 | func permittedCaps() (uint64, error) { 26 | data, err := ioutil.ReadFile("/proc/self/status") 27 | if err != nil { 28 | return 0, err 29 | } 30 | const prefix = "CapPrm:" 31 | for _, line := range strings.Split(string(data), "\n") { 32 | if strings.HasPrefix(line, prefix) { 33 | return strconv.ParseUint(strings.TrimSpace(line[len(prefix):]), 16, 64) 34 | } 35 | } 36 | return 0, fmt.Errorf("didn't find %#v in /proc/self/status", prefix) 37 | } 38 | 39 | func main() { 40 | caps, err := permittedCaps() 41 | if err != nil { 42 | fmt.Println(err) 43 | os.Exit(1) 44 | } 45 | if caps == 0 { 46 | fmt.Println("No capabilities") 47 | } else { 48 | fmt.Printf("Has capabilities (%x)\n", caps) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/build-configs/caps/go.mod: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module example.com/caps 16 | 17 | go 1.16 18 | -------------------------------------------------------------------------------- /test/build-configs/foo/cmd/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import "fmt" 18 | 19 | func main() { 20 | fmt.Println("foo") 21 | } 22 | -------------------------------------------------------------------------------- /test/build-configs/foo/go.mod: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module example.com/foo 16 | 17 | go 1.16 18 | -------------------------------------------------------------------------------- /test/build-configs/toolexec/cmd/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import "fmt" 18 | 19 | func main() { 20 | fmt.Println("toolexec") 21 | } 22 | -------------------------------------------------------------------------------- /test/build-configs/toolexec/go.mod: -------------------------------------------------------------------------------- 1 | // Copyright 2022 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module example.com/toolexec 16 | 17 | go 1.16 18 | -------------------------------------------------------------------------------- /test/kodata/a: -------------------------------------------------------------------------------- 1 | hello 2 | -------------------------------------------------------------------------------- /test/kodata/b: -------------------------------------------------------------------------------- 1 | a -------------------------------------------------------------------------------- /test/kodata/kenobi: -------------------------------------------------------------------------------- 1 | Hello there 2 | -------------------------------------------------------------------------------- /test/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 ko Build Authors All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "log" 21 | "os" 22 | "os/signal" 23 | "path/filepath" 24 | "runtime" 25 | "syscall" 26 | "time" 27 | 28 | // Give this an interesting import 29 | _ "github.com/google/go-containerregistry/pkg/registry" 30 | ) 31 | 32 | var ( 33 | f = flag.String("f", "kenobi", "File in kodata to print") 34 | wait = flag.Bool("wait", false, "Whether to wait for SIGTERM") 35 | ) 36 | 37 | // This is defined so we can test build-time variable setting using ldflags. 38 | var version = "default" 39 | 40 | func main() { 41 | flag.Parse() 42 | 43 | log.Println("version =", version) 44 | 45 | if runtime.GOOS == "windows" { 46 | // Go seems to not load location data from Windows, so timezone 47 | // conversion fails unless tzdata is embedded in the Go program 48 | // with the go build tag `timetzdata`. Since we want to test 49 | // loading tzdata provided by the base image below, we'll just 50 | // skip that for Windows here. 51 | // See https://github.com/ko-build/ko/issues/739 52 | log.Println("skipping timezone conversion on Windows") 53 | } else { 54 | // Exercise timezone conversions, which demonstrates tzdata is provided 55 | // by the base image. 56 | now := time.Now() 57 | loc, _ := time.LoadLocation("UTC") 58 | fmt.Printf("UTC Time: %s\n", now.In(loc)) 59 | loc, _ = time.LoadLocation("America/New_York") 60 | fmt.Printf("New York Time: %s\n", now.In(loc)) 61 | } 62 | 63 | dp := os.Getenv("KO_DATA_PATH") 64 | file := filepath.Join(dp, *f) 65 | bytes, err := os.ReadFile(file) 66 | if err != nil { 67 | log.Fatalf("Error reading %q: %v", file, err) 68 | } 69 | fmt.Println(string(bytes)) 70 | 71 | // Cause the pod to "hang" to allow us to check for a readiness state. 72 | if *wait { 73 | sigs := make(chan os.Signal, 1) 74 | signal.Notify(sigs, syscall.SIGTERM) 75 | <-sigs 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/test.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 ko Build Authors All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | apiVersion: v1 15 | kind: Pod 16 | metadata: 17 | name: kodata 18 | spec: 19 | containers: 20 | - name: obiwan 21 | image: ko://github.com/google/ko/test 22 | args: 23 | - --wait=true 24 | restartPolicy: Never 25 | --------------------------------------------------------------------------------