├── .codecov.yml ├── .gitattributes ├── .github ├── actions │ └── pack-build │ │ ├── action.yml │ │ └── report.go ├── dependabot.yml └── workflows │ ├── ci.yaml │ ├── renovate.yaml │ └── unit-test.yaml ├── .gitignore ├── ADOPTERS.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── DEVELOPMENT.md ├── GOVERNANCE.md ├── LICENSE ├── MAINTAINERS.md ├── Makefile ├── NOTICE ├── PacmanFile ├── README.md ├── RELEASE.md ├── SECURITY.md ├── SUPPORT.md ├── Tiltfile ├── VERSIONING.md ├── api └── openapi-spec │ └── swagger.json ├── cmd ├── build-init │ └── main.go ├── build-waiter │ └── main.go ├── completion │ └── main.go ├── controller │ └── main.go ├── logs │ └── main.go ├── rebase │ └── main.go ├── version.go └── webhook │ └── main.go ├── config ├── 100-namespace.yaml ├── build.yaml ├── builder.yaml ├── buildpack.yaml ├── clusterbuilder.yaml ├── clusterbuildpack.yaml ├── clusterlifecycle.yaml ├── clusterstack.yaml ├── clusterstore.yaml ├── config-logging.yaml ├── controller.yaml ├── controllerrole.yaml ├── default_clusterlifecycle.yaml ├── image.yaml ├── priority.yaml ├── schema.yaml ├── service.yaml ├── sourceresolver.yaml ├── webhook.yaml └── webhookrole.yaml ├── docs ├── assets │ ├── k8s-components.png │ ├── k8s-control-loop.png │ ├── kpack-big-white.png │ ├── kpack-big.png │ ├── kpack-control-loop.png │ ├── kpack-high-level.jpg │ ├── kpack-stack-change.jpg │ ├── kpack-white.png │ ├── kpack.png │ └── node-min.gif ├── build.md ├── builders.md ├── buildpacks.md ├── bump-build-api.md ├── cosign-tutorial.md ├── image.md ├── injected_sidecars.md ├── install.md ├── kpack-vs-pack.md ├── legacy-cnb-servicebindings.md ├── logs.md ├── overview.md ├── secrets.md ├── servicebindings.md ├── slsa.md ├── slsa_build.md ├── stack.md ├── tutorial.md └── tutorial_kp.md ├── go.mod ├── go.sum ├── hack ├── boilerplate │ ├── boilerplate.go.txt │ └── boilerplate.sh.txt ├── build.sh ├── deploytilt.sh ├── local.sh ├── openapi-codegen.sh ├── openapi │ └── main.go ├── tools.go └── update-codegen.sh ├── internal └── logrus │ └── fatal │ └── fatal_level.go ├── pkg ├── apis │ ├── build │ │ ├── register.go │ │ ├── v1alpha1 │ │ │ ├── build.go │ │ │ ├── build_lifecycle.go │ │ │ ├── build_test.go │ │ │ ├── build_types.go │ │ │ ├── build_validation.go │ │ │ ├── build_validation_test.go │ │ │ ├── builder_lifecycle.go │ │ │ ├── builder_resource.go │ │ │ ├── builder_types.go │ │ │ ├── builder_validation.go │ │ │ ├── builder_validation_test.go │ │ │ ├── cluster_builder_types.go │ │ │ ├── cluster_builder_validation.go │ │ │ ├── cluster_builder_validation_test.go │ │ │ ├── cluster_stack_types.go │ │ │ ├── cluster_stack_validation.go │ │ │ ├── cluster_stack_validation_test.go │ │ │ ├── cluster_store_types.go │ │ │ ├── cluster_store_validation.go │ │ │ ├── cluster_store_validation_test.go │ │ │ ├── doc.go │ │ │ ├── image_builds.go │ │ │ ├── image_lifecycle.go │ │ │ ├── image_types.go │ │ │ ├── image_validation.go │ │ │ ├── image_validation_test.go │ │ │ ├── register.go │ │ │ ├── source_resolver.go │ │ │ ├── source_resolver_types.go │ │ │ └── zz_generated.deepcopy.go │ │ └── v1alpha2 │ │ │ ├── build.go │ │ │ ├── build_conversion.go │ │ │ ├── build_conversion_test.go │ │ │ ├── build_lifecycle.go │ │ │ ├── build_pod.go │ │ │ ├── build_pod_test.go │ │ │ ├── build_priority.go │ │ │ ├── build_test.go │ │ │ ├── build_types.go │ │ │ ├── build_validation.go │ │ │ ├── build_validation_test.go │ │ │ ├── builder_conversion.go │ │ │ ├── builder_conversion_test.go │ │ │ ├── builder_lifecycle.go │ │ │ ├── builder_resource.go │ │ │ ├── builder_types.go │ │ │ ├── builder_validation.go │ │ │ ├── builder_validation_test.go │ │ │ ├── buildpack_types.go │ │ │ ├── buildpack_validation.go │ │ │ ├── buildpack_validation_test.go │ │ │ ├── cluster_builder_conversion.go │ │ │ ├── cluster_builder_conversion_test.go │ │ │ ├── cluster_builder_types.go │ │ │ ├── cluster_builder_validation.go │ │ │ ├── cluster_builder_validation_test.go │ │ │ ├── cluster_buildpack_types.go │ │ │ ├── cluster_buildpack_validation.go │ │ │ ├── cluster_buildpack_validation_test.go │ │ │ ├── cluster_lifecycle_types.go │ │ │ ├── cluster_lifecycle_validation.go │ │ │ ├── cluster_stack_conversion.go │ │ │ ├── cluster_stack_conversion_test.go │ │ │ ├── cluster_stack_types.go │ │ │ ├── cluster_stack_validation.go │ │ │ ├── cluster_stack_validation_test.go │ │ │ ├── cluster_store_conversion.go │ │ │ ├── cluster_store_conversion_test.go │ │ │ ├── cluster_store_types.go │ │ │ ├── cluster_store_validation.go │ │ │ ├── cluster_store_validation_test.go │ │ │ ├── cosign_types.go │ │ │ ├── cosign_validation.go │ │ │ ├── doc.go │ │ │ ├── image_builds.go │ │ │ ├── image_builds_test.go │ │ │ ├── image_conversion.go │ │ │ ├── image_conversion_test.go │ │ │ ├── image_lifecycle.go │ │ │ ├── image_types.go │ │ │ ├── image_validation.go │ │ │ ├── image_validation_test.go │ │ │ ├── register.go │ │ │ ├── source_resolver.go │ │ │ ├── source_resolver_conversion.go │ │ │ ├── source_resolver_conversion_test.go │ │ │ ├── source_resolver_types.go │ │ │ └── zz_generated.deepcopy.go │ ├── core │ │ └── v1alpha1 │ │ │ ├── build_types.go │ │ │ ├── build_validation.go │ │ │ ├── buildpack_metadata.go │ │ │ ├── buildpack_types.go │ │ │ ├── buildpackage_types.go │ │ │ ├── condition_types.go │ │ │ ├── image_types.go │ │ │ ├── notary_types.go │ │ │ ├── notary_validation.go │ │ │ ├── service_bindings.go │ │ │ ├── source_types.go │ │ │ ├── source_validation.go │ │ │ ├── status.go │ │ │ ├── volatile_time.go │ │ │ └── zz_generated.deepcopy.go │ └── validate │ │ └── validation_helpers.go ├── arch_test.go ├── archive │ └── archive.go ├── blob │ ├── azure_keychain.go │ ├── fetch.go │ ├── fetch_test.go │ ├── file_keychain.go │ ├── file_keychain_test.go │ ├── gcp_keychain.go │ ├── keychain.go │ ├── keychain_test.go │ ├── resolver.go │ └── testdata │ │ ├── fat-zip.zip │ │ ├── parent.tar │ │ ├── parent.tar.gz │ │ ├── parent.zip │ │ ├── test-exe.tar │ │ ├── test.html │ │ ├── test.tar │ │ ├── test.tar.gz │ │ ├── test.txt │ │ └── test.zip ├── buildchange │ ├── buildpack_change.go │ ├── change.go │ ├── change_logger.go │ ├── change_logger_test.go │ ├── change_processor.go │ ├── change_processor_test.go │ ├── change_summary.go │ ├── commit_change.go │ ├── config_change.go │ ├── lifecycle_change.go │ ├── progress_logger.go │ ├── progress_logger_test.go │ ├── stack_change.go │ └── trigger_change.go ├── buildpod │ ├── generator.go │ └── generator_test.go ├── client │ ├── clientset │ │ └── versioned │ │ │ ├── clientset.go │ │ │ ├── fake │ │ │ ├── clientset_generated.go │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ ├── scheme │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ └── typed │ │ │ └── build │ │ │ ├── v1alpha1 │ │ │ ├── build.go │ │ │ ├── build_client.go │ │ │ ├── builder.go │ │ │ ├── clusterbuilder.go │ │ │ ├── clusterstack.go │ │ │ ├── clusterstore.go │ │ │ ├── doc.go │ │ │ ├── fake │ │ │ │ ├── doc.go │ │ │ │ ├── fake_build.go │ │ │ │ ├── fake_build_client.go │ │ │ │ ├── fake_builder.go │ │ │ │ ├── fake_clusterbuilder.go │ │ │ │ ├── fake_clusterstack.go │ │ │ │ ├── fake_clusterstore.go │ │ │ │ ├── fake_image.go │ │ │ │ └── fake_sourceresolver.go │ │ │ ├── generated_expansion.go │ │ │ ├── image.go │ │ │ └── sourceresolver.go │ │ │ └── v1alpha2 │ │ │ ├── build.go │ │ │ ├── build_client.go │ │ │ ├── builder.go │ │ │ ├── buildpack.go │ │ │ ├── clusterbuilder.go │ │ │ ├── clusterbuildpack.go │ │ │ ├── clusterlifecycle.go │ │ │ ├── clusterstack.go │ │ │ ├── clusterstore.go │ │ │ ├── doc.go │ │ │ ├── fake │ │ │ ├── doc.go │ │ │ ├── fake_build.go │ │ │ ├── fake_build_client.go │ │ │ ├── fake_builder.go │ │ │ ├── fake_buildpack.go │ │ │ ├── fake_clusterbuilder.go │ │ │ ├── fake_clusterbuildpack.go │ │ │ ├── fake_clusterlifecycle.go │ │ │ ├── fake_clusterstack.go │ │ │ ├── fake_clusterstore.go │ │ │ ├── fake_image.go │ │ │ └── fake_sourceresolver.go │ │ │ ├── generated_expansion.go │ │ │ ├── image.go │ │ │ └── sourceresolver.go │ ├── informers │ │ └── externalversions │ │ │ ├── build │ │ │ ├── interface.go │ │ │ ├── v1alpha1 │ │ │ │ ├── build.go │ │ │ │ ├── builder.go │ │ │ │ ├── clusterbuilder.go │ │ │ │ ├── clusterstack.go │ │ │ │ ├── clusterstore.go │ │ │ │ ├── image.go │ │ │ │ ├── interface.go │ │ │ │ └── sourceresolver.go │ │ │ └── v1alpha2 │ │ │ │ ├── build.go │ │ │ │ ├── builder.go │ │ │ │ ├── buildpack.go │ │ │ │ ├── clusterbuilder.go │ │ │ │ ├── clusterbuildpack.go │ │ │ │ ├── clusterlifecycle.go │ │ │ │ ├── clusterstack.go │ │ │ │ ├── clusterstore.go │ │ │ │ ├── image.go │ │ │ │ ├── interface.go │ │ │ │ └── sourceresolver.go │ │ │ ├── factory.go │ │ │ ├── generic.go │ │ │ └── internalinterfaces │ │ │ └── factory_interfaces.go │ └── listers │ │ └── build │ │ ├── v1alpha1 │ │ ├── build.go │ │ ├── builder.go │ │ ├── clusterbuilder.go │ │ ├── clusterstack.go │ │ ├── clusterstore.go │ │ ├── expansion_generated.go │ │ ├── image.go │ │ └── sourceresolver.go │ │ └── v1alpha2 │ │ ├── build.go │ │ ├── builder.go │ │ ├── buildpack.go │ │ ├── clusterbuilder.go │ │ ├── clusterbuildpack.go │ │ ├── clusterlifecycle.go │ │ ├── clusterstack.go │ │ ├── clusterstore.go │ │ ├── expansion_generated.go │ │ ├── image.go │ │ └── sourceresolver.go ├── cnb │ ├── build_metadata.go │ ├── build_metadata_test.go │ ├── builder_builder.go │ ├── builder_builder_test.go │ ├── builder_layers.go │ ├── buildpack_metadata.go │ ├── buildpack_resolver.go │ ├── buildpack_resolver_test.go │ ├── buildpack_validation.go │ ├── buildpackage_metadata.go │ ├── create_builder.go │ ├── create_builder_test.go │ ├── dependency_tree.go │ ├── dependency_tree_test.go │ ├── env_vars.go │ ├── fakes_test.go │ ├── platform_env_vars_setup.go │ ├── platform_env_vars_setup_test.go │ ├── project_descriptor.go │ ├── project_descriptor_test.go │ ├── remote_buildpack_fetcher.go │ ├── remote_buildpack_fetcher_test.go │ ├── remote_buildpack_metadata.go │ ├── remote_lifecycle_reader.go │ ├── remote_lifecycle_reader_test.go │ ├── remote_stack_reader.go │ ├── remote_stack_reader_test.go │ ├── remote_store_reader.go │ └── remote_store_reader_test.go ├── config │ └── config.go ├── cosign │ ├── image_signer.go │ ├── image_signer_test.go │ └── testing │ │ └── test_util.go ├── differ │ ├── differ.go │ └── differ_test.go ├── dockercreds │ ├── access_checker.go │ ├── access_checker_test.go │ ├── cached_keychain.go │ ├── cached_keychain_test.go │ ├── docker_creds.go │ ├── docker_creds_test.go │ ├── k8sdockercreds │ │ ├── azurecredentialhelperfix │ │ │ ├── keychain.go │ │ │ └── setup.go │ │ ├── k8s_keychain.go │ │ └── k8s_keychain_test.go │ ├── match.go │ ├── match_test.go │ ├── parse_secrets.go │ ├── parse_secrets_test.go │ ├── volume_secret_keychain.go │ └── volume_secret_keychain_test.go ├── duckbuilder │ ├── duck_builder.go │ ├── duck_builder_test.go │ ├── informer.go │ └── informer_test.go ├── duckprovisionedserviceable │ ├── fake │ │ └── fakeprovisionedservice.go │ └── provisionedservicable.go ├── flaghelpers │ └── credential_flags.go ├── git │ ├── fetch.go │ ├── fetch_test.go │ ├── git_keychain.go │ ├── git_keychain_test.go │ ├── k8s_git_keychain.go │ ├── k8s_git_keychain_test.go │ ├── remote_git_resolver.go │ ├── remote_git_resolver_test.go │ ├── resolver.go │ ├── url_parser.go │ └── url_parser_test.go ├── logs │ ├── build_logs.go │ ├── wait_for_image.go │ ├── wait_for_image_test.go │ ├── watch_build.go │ └── watch_image.go ├── notary │ ├── authenticating_round_tripper.go │ ├── authenticating_round_tripper_test.go │ ├── image_signer.go │ ├── image_signer_test.go │ ├── repository.go │ └── testdata │ │ ├── notary-no-key │ │ └── password │ │ ├── notary │ │ ├── 2a7690c4784f2c62770a27e1ae807ecf1f0bd0501c11bb68a0bab2e8ccdee24f.key │ │ └── password │ │ ├── report-multiple-gun.toml │ │ └── report.toml ├── openapi │ └── generated.openapi.go ├── reconciler │ ├── build │ │ ├── build.go │ │ ├── build_test.go │ │ ├── buildfakes │ │ │ ├── fake_metadata_retriever.go │ │ │ ├── fake_secret_fetcher.go │ │ │ └── fake_slsaattester.go │ │ └── sort.go │ ├── builder │ │ ├── builder.go │ │ └── builder_test.go │ ├── buildpack │ │ ├── buildpack.go │ │ ├── buildpack_test.go │ │ └── buildpackfakes │ │ │ └── fake_store_reader.go │ ├── clusterbuilder │ │ ├── clusterbuilder.go │ │ └── clusterbuilder_test.go │ ├── clusterbuildpack │ │ ├── clusterbuildpack.go │ │ ├── clusterbuildpack_test.go │ │ └── clusterbuildpackfakes │ │ │ └── fake_store_reader.go │ ├── clusterlifecycle │ │ ├── clusterlifecycle.go │ │ ├── clusterlifecycle_test.go │ │ └── clusterlifecyclefakes │ │ │ └── fake_cluster_lifecycle_reader.go │ ├── clusterstack │ │ ├── clusterstack.go │ │ ├── clusterstack_test.go │ │ └── clusterstackfakes │ │ │ └── fake_cluster_stack_reader.go │ ├── clusterstore │ │ ├── clusterstore.go │ │ ├── clusterstore_test.go │ │ └── clusterstorefakes │ │ │ └── fake_store_reader.go │ ├── filter.go │ ├── filter_test.go │ ├── image │ │ ├── build_list.go │ │ ├── build_required.go │ │ ├── build_required_test.go │ │ ├── image.go │ │ ├── image_test.go │ │ └── reconcile_build.go │ ├── network_error_reconciler.go │ ├── network_error_reconciler_test.go │ ├── options.go │ ├── sourceresolver │ │ ├── enqueuer.go │ │ ├── enqueuer_test.go │ │ ├── sourceresolver.go │ │ ├── sourceresolver_test.go │ │ └── sourceresolverfakes │ │ │ ├── fake_enqueuer.go │ │ │ └── fake_resolver.go │ ├── testhelpers │ │ ├── diff_out_builder.go │ │ ├── fake_builder_creator.go │ │ ├── fake_tracker.go │ │ ├── json_compactor.go │ │ ├── listers.go │ │ └── reconciler_tester.go │ └── tracker.go ├── registry │ ├── client.go │ ├── client_test.go │ ├── fetch.go │ ├── fetch_test.go │ ├── imagehelpers │ │ ├── helpers.go │ │ ├── helpers_test.go │ │ ├── lazy_layer.go │ │ └── lazy_layer_test.go │ ├── keychain_factory.go │ ├── registryfakes │ │ ├── fake_client.go │ │ └── fake_keychain_factory.go │ ├── resolver.go │ └── testdata │ │ ├── layer.tar │ │ ├── reg.tar │ │ ├── tar.tar │ │ ├── tarexe.tar │ │ ├── targz.tar │ │ └── zip.tar ├── secret │ ├── constants.go │ ├── fetcher.go │ ├── secret_types.go │ ├── secretfakes │ │ └── fake_fetcher.go │ ├── signing_secret.go │ ├── signing_secret_test.go │ ├── volume_secret_reader.go │ └── volume_secret_reader_test.go ├── slsa │ ├── attest.go │ ├── attest_test.go │ ├── cosign_signer.go │ ├── image.go │ ├── image_test.go │ ├── pkcs8_signer.go │ ├── sign.go │ └── sign_test.go └── tracker │ ├── tracker.go │ └── tracker_test.go ├── rfcs ├── 0000-template.md ├── 0001-remove-builders.md ├── 0002-reason-message-diff.md ├── 0003-windows-images.md ├── 0004-image-filter.md ├── 0005-notary-integration.md ├── 0006-upgradeable-lifecycle.md ├── 0007-cosign-integration.md ├── 0008-image-build-node-selection.md ├── 0009-buildpack-resource.md ├── 0010-support-injected-sidecars.md ├── 0011-slsa-attestation.md ├── 0012-remove-windows-support.md └── README.md ├── samples ├── build.yaml ├── builder.yaml ├── cluster_builder.yaml ├── cluster_stack.yaml ├── cluster_store.yaml ├── image_from_blob_url.yaml ├── image_from_git.yaml ├── image_with_cache.yaml ├── image_with_legacy_cnb_service_bindings.yaml └── image_with_service_bindings.yaml └── test ├── config.go ├── cosign_e2e_test.go ├── e2e.go ├── execute_build_test.go ├── slsa_test.go └── testhelpers.go /.codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "**/zz_*_generated.go" # Ignore generated files. 3 | - "**/zz_generated.deepcopy.go" # Ignore generated files. -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # This file is documented at https://git-scm.com/docs/gitattributes. 2 | # Linguist-specific attributes are documented at 3 | # https://github.com/github/linguist. 4 | 5 | **/zz_generated.*.go linguist-generated=true 6 | /pkg/client/** linguist-generated=true 7 | /test/client/** linguist-generated=true 8 | 9 | # coverage-excluded is an attribute used to explicitly exclude a path from being included in code 10 | # coverage. If a path is marked as linguist-generated already, it will be implicitly excluded and 11 | # there is no need to add the coverage-excluded attribute 12 | /pkg/**/testing/** coverage-excluded=true 13 | /vendor/** coverage-excluded=true 14 | /test/** coverage-excluded=true 15 | /cmd/**/main.go coverage-excluded=true 16 | -------------------------------------------------------------------------------- /.github/actions/pack-build/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Pack Build' 2 | description: 'Pack build kpack images' 3 | 4 | inputs: 5 | tag: 6 | description: 'location to write image' 7 | required: true 8 | bp_go_targets: 9 | description: 'value of BP_GO_TARGETS env' 10 | builder: 11 | description: 'builder image' 12 | required: true 13 | default: 'paketobuildpacks/builder-jammy-tiny' 14 | pack_version: 15 | description: 'version of pack to use' 16 | required: true 17 | additional_pack_args: 18 | description: 'additional args for pack' 19 | artifact_name: 20 | description: 'name of the artifact to upload' 21 | required: true 22 | 23 | runs: 24 | using: "composite" 25 | steps: 26 | - name: Set up Go 27 | uses: actions/setup-go@v4 28 | with: 29 | go-version-file: 'go.mod' 30 | - name: setup-pack 31 | uses: buildpacks/github-actions/setup-pack@v5.0.0 32 | with: 33 | pack-version: ${{ inputs.pack_version }} 34 | - name: build 35 | shell: bash 36 | run: | 37 | [[ $GITHUB_REF =~ ^refs\/tags\/v(.*)$ ]] && version=${BASH_REMATCH[1]} || version=0.0.0 38 | 39 | KPACK_VERSION=$version 40 | KPACK_COMMIT=$GITHUB_SHA 41 | mkdir report 42 | 43 | export PATH="$PATH:$(pwd)" 44 | pack build ${{ inputs.tag }} \ 45 | --builder ${{ inputs.builder }} \ 46 | --env BP_GO_LDFLAGS="-X 'github.com/pivotal/kpack/cmd.Version=${KPACK_VERSION}' -X 'github.com/pivotal/kpack/cmd.Commit=${KPACK_COMMIT}'" \ 47 | --env BP_GO_TARGETS="${{ inputs.bp_go_targets }}" \ 48 | --report-output-dir . \ 49 | --cache-image ${{ inputs.tag }}-cache \ 50 | --publish ${{ inputs.additional_pack_args }} 51 | 52 | mkdir images 53 | digest=$(go run .github/actions/pack-build/report.go -path ./report.toml) 54 | name=$(basename ${{ inputs.tag }}) 55 | echo "${{ inputs.tag }}@${digest}" > images/$name 56 | cat images/$name 57 | - name: Upload Image Artifacts 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: ${{ inputs.artifact_name }} 61 | path: images/ 62 | -------------------------------------------------------------------------------- /.github/actions/pack-build/report.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/BurntSushi/toml" 9 | ) 10 | 11 | var reportFilePath = flag.String("path", "report/report.toml", "path to report.toml") 12 | 13 | func main() { 14 | flag.Parse() 15 | 16 | report := struct { 17 | Image struct { 18 | Digest string `toml:"digest,omitempty"` 19 | } `toml:"image"` 20 | }{} 21 | _, err := toml.DecodeFile(*reportFilePath, &report) 22 | if err != nil { 23 | log.Fatal(err, "error decoding report toml file") 24 | } 25 | 26 | fmt.Println(report.Image.Digest) 27 | } 28 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Dependencies listed in go.mod 4 | - package-ecosystem: "gomod" 5 | directory: "/" # Location of package manifests 6 | schedule: 7 | interval: "weekly" 8 | ignore: 9 | - dependency-name: "k8s.io/api" 10 | - dependency-name: "k8s.io/apimachinery" 11 | - dependency-name: "k8s.io/client-go" 12 | 13 | # Dependencies listed in .github/workflows/*.yml 14 | - package-ecosystem: "github-actions" 15 | directory: "/" 16 | schedule: 17 | interval: "weekly" 18 | -------------------------------------------------------------------------------- /.github/workflows/renovate.yaml: -------------------------------------------------------------------------------- 1 | name: Renovate 2 | 3 | on: 4 | schedule: 5 | - cron: '0 */12 * * *' 6 | 7 | jobs: 8 | renovate_main: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4.1.1 13 | - name: Self-hosted Renovate 14 | uses: renovatebot/github-action@v41.0.8 15 | with: 16 | token: ${{ secrets.RELEASE_TOKEN }} 17 | env: 18 | RENOVATE_REPOSITORIES: "buildpacks-community/kpack" 19 | RENOVATE_PLATFORM: github 20 | RENOVATE_PRUNE_STALE_BRANCHES: false 21 | RENOVATE_PR_HOURLY_LIMIT: 10 22 | LOG_LEVEL: DEBUG 23 | RENOVATE_ADD_LABELS: dependencies 24 | 25 | renovate_branches: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v4.1.1 30 | - name: Self-hosted Renovate 31 | uses: renovatebot/github-action@v41.0.8 32 | with: 33 | token: ${{ secrets.RELEASE_TOKEN }} 34 | env: 35 | RENOVATE_REPOSITORIES: "buildpacks-community/kpack" 36 | RENOVATE_PLATFORM: github 37 | RENOVATE_PRUNE_STALE_BRANCHES: false 38 | RENOVATE_PR_HOURLY_LIMIT: 10 39 | LOG_LEVEL: DEBUG 40 | RENOVATE_ADD_LABELS: dependencies 41 | RENOVATE_BASE_BRANCHES: '["release/v0.12.x", "release/v0.11.x", "release/v0.10.x", "release/v0.9.x"]' 42 | RENOVATE_PACKAGE_RULES: | 43 | [ 44 | { 45 | "groupName": "all patch dependencies", 46 | "groupSlug": "all-patch", 47 | "matchPackagePatterns": ["*"], 48 | "matchUpdateTypes": ["patch"] 49 | } 50 | ] -------------------------------------------------------------------------------- /.github/workflows/unit-test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | with: 12 | fetch-depth: 0 13 | - name: Set up Go 14 | uses: actions/setup-go@v5 15 | with: 16 | go-version-file: 'go.mod' 17 | - name: Run tests 18 | run: make unit-ci 19 | - name: Report coverage 20 | uses: codecov/codecov-action@v4.5.0 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Operating system temporary files 2 | .DS_Store 3 | 4 | # Editor/IDE specific settings 5 | .idea 6 | .vscode/ 7 | 8 | # Temporary output of build tools 9 | bazel-* 10 | *.out 11 | coverage.txt 12 | 13 | vendor 14 | 15 | pkg/openapi/openapi_generated.go.old 16 | -------------------------------------------------------------------------------- /ADOPTERS.md: -------------------------------------------------------------------------------- 1 | # Who's using Kpack? 2 | 3 | ### Adopters 4 | Below is a list of projects that have publicly adopted kpack. 5 | 6 | * [Bloomberg](https://www.techatbloomberg.com/) 7 | * [VMware Tanzu Build Service](https://tanzu.vmware.com/build-service) 8 | * [Azure Spring Apps](https://azure.microsoft.com/en-us/products/spring-apps) 9 | * [Korifi](https://www.cloudfoundry.org/technology/korifi/) 10 | 11 | If you'd like to be added, feel free to [open a pull-request!](https://github.com/buildpacks-community/kpack/pulls) 12 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @buildpacks-community/kpack-maintainers 2 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | ## Policies 2 | 3 | This repository adheres to the following project policies: 4 | 5 | - [Code of Conduct](CODE_OF_CONDUCT.md) - How we should act with each other. 6 | - [Contributing](CONTRIBUTING.md) - General contributing standards. 7 | - [Security](SECURITY.md) - Reporting security concerns. 8 | - [Support](SUPPORT.md) - Getting support. 9 | 10 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | The current Maintainers Group for the kpack Project consists of: 2 | 3 | | Name | Employer | Responsibilities | 4 | |-----------------|-----------|------------------| 5 | | Tom Kennedy | VMware | | 6 | | Matthew McNew | | | 7 | | Daniel Chen | VMware | | 8 | | Sambhav Kothari | Bloomberg | | 9 | 10 | 11 | See [the project Governance](GOVERNANCE.md) for how maintainers are selected and replaced. 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go parameters 2 | GOCMD?=go 3 | 4 | all: unit 5 | 6 | unit: 7 | $(GOCMD) test ./pkg/... 8 | 9 | unit-ci: 10 | $(GOCMD) test ./pkg/... -coverprofile=coverage.txt -covermode=atomic 11 | 12 | e2e: 13 | $(GOCMD) test --timeout=30m -v ./test/... 14 | 15 | .PHONY: unit unit-ci e2e 16 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2020-Present VMware, Inc. 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 | -------------------------------------------------------------------------------- /PacmanFile: -------------------------------------------------------------------------------- 1 | mingw-w64-x86_64-libgit2 2 | mingw-w64-x86_64-gcc 3 | mingw-w64-x86_64-pkg-config -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | We strongly encourage people to report security vulnerabilities privately to our security team before disclosing them in a public forum. 6 | 7 | Please note that the e-mail address below should only be used for reporting undisclosed security vulnerabilities in Cloud Native Buildpacks products and managing the process of fixing such vulnerabilities. We cannot accept regular bug reports or other security related queries at this address. 8 | 9 | The e-mail address to use to contact the Cloud Native Buildpacks Security Team is security@buildpacks.io. 10 | 11 | The fingerprint is: `7AA4 452E A0C3 56F8 894D C869 4E56 F857 5412 6F64` 12 | 13 | It can be obtained from a public key server such as pgp.mit.edu. 14 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support Policy 2 | 3 | ## Security Vulnerabilities 4 | If you believe that you've found a security vulnerability, please check [SECURITY.md](SECURITY.md) to learn how to disclose responsibly. 5 | 6 | ## GitHub Issues 7 | We choose not to use GitHub issues for general usage questions and support, preferring to use issues solely for the tracking of bugs and enhancements. If you have a general usage question please do not open a GitHub issue, but use one of the other channels described below. 8 | 9 | If you are reporting a bug, please help to speed up problem diagnosis by providing as much information as possible. Ideally, that would include a small sample project that reproduces the problem.. 10 | 11 | ## Slack 12 | The kpack community monitors [CNCF slack](https://cloud-native.slack.com/channels/buildpacks-kpack). Before asking a question please search the history to see if the question has already been asked and answered. 13 | -------------------------------------------------------------------------------- /VERSIONING.md: -------------------------------------------------------------------------------- 1 | # Kpack Release Versioning 2 | 3 | Reference: 4 | - [Semantic Versioning](http://semver.org) 5 | - [Kubernetes Resource Versioning](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/) 6 | 7 | Legend: 8 | 9 | - kpack follows semantic versioning rules, where **X.Y.Z** refers to the version (git tag) of kpack that is released. 10 | (**X** is the major version, **Y** is the minor version, and **Z** is the patch version.) 11 | 12 | - kpack Custom Resource Definition (CRDs) follows the kubernetes resources versioning 13 | -------------------------------------------------------------------------------- /cmd/logs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | "os" 8 | 9 | "k8s.io/client-go/kubernetes" 10 | _ "k8s.io/client-go/plugin/pkg/client/auth" 11 | "k8s.io/client-go/rest" 12 | "k8s.io/client-go/tools/clientcmd" 13 | "k8s.io/client-go/tools/clientcmd/api" 14 | 15 | "github.com/pivotal/kpack/pkg/logs" 16 | ) 17 | 18 | var ( 19 | kubeconfig = flag.String("kubeconfig", "", "Path to a kubeconfig.") 20 | masterURL = flag.String("master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig.") 21 | image = flag.String("image", "", "The image name to tail logs") 22 | build = flag.String("build", "", "The build number to tail logs") 23 | namespace = flag.String("namespace", "default", "The namespace of the image") 24 | ) 25 | 26 | func main() { 27 | flag.Parse() 28 | 29 | clusterConfig, err := BuildConfigFromFlags(*masterURL, *kubeconfig) 30 | if err != nil { 31 | log.Fatalf("Error building kubeconfig: %v", err) 32 | } 33 | 34 | k8sClient, err := kubernetes.NewForConfig(clusterConfig) 35 | if err != nil { 36 | log.Fatalf("could not get kubernetes client: %s", err.Error()) 37 | } 38 | 39 | if (*build) == "" { 40 | err = logs.NewBuildLogsClient(k8sClient).TailImage(context.Background(), os.Stdout, *image, *namespace) 41 | } else { 42 | err = logs.NewBuildLogsClient(k8sClient).Tail(context.Background(), os.Stdout, *image, *build, *namespace, false) 43 | } 44 | 45 | if err != nil { 46 | log.Fatalf("error tailing logs %s", err) 47 | } 48 | 49 | } 50 | 51 | func BuildConfigFromFlags(masterURL, kubeconfigPath string) (*rest.Config, error) { 52 | 53 | var clientConfigLoader clientcmd.ClientConfigLoader 54 | 55 | if kubeconfigPath == "" { 56 | clientConfigLoader = clientcmd.NewDefaultClientConfigLoadingRules() 57 | } else { 58 | clientConfigLoader = &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath} 59 | } 60 | 61 | return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 62 | clientConfigLoader, 63 | &clientcmd.ConfigOverrides{ClusterInfo: api.Cluster{Server: masterURL}}).ClientConfig() 64 | 65 | } 66 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import "fmt" 4 | 5 | // This is set by CI when creating release binaries 6 | var ( 7 | Version = "0.0.0" 8 | Commit = "000" 9 | Identifer = fmt.Sprintf("v%s (git sha: %s)", Version, Commit) 10 | ) 11 | -------------------------------------------------------------------------------- /config/100-namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: kpack 5 | labels: 6 | pod-security.kubernetes.io/enforce: restricted 7 | pod-security.kubernetes.io/enforce-version: v1.25 -------------------------------------------------------------------------------- /config/build.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: builds.kpack.io 5 | spec: 6 | group: kpack.io 7 | versions: 8 | - name: v1alpha1 9 | served: true 10 | storage: false 11 | schema: 12 | openAPIV3Schema: 13 | type: object 14 | x-kubernetes-preserve-unknown-fields: true 15 | subresources: 16 | status: { } 17 | additionalPrinterColumns: 18 | - name: Image 19 | type: string 20 | jsonPath: ".status.latestImage" 21 | - name: Succeeded 22 | type: string 23 | jsonPath: ".status.conditions[?(@.type==\"Succeeded\")].status" 24 | - name: v1alpha2 25 | served: true 26 | storage: true 27 | schema: 28 | openAPIV3Schema: 29 | type: object 30 | x-kubernetes-preserve-unknown-fields: true 31 | subresources: 32 | status: { } 33 | additionalPrinterColumns: 34 | - name: Image 35 | type: string 36 | jsonPath: ".status.latestImage" 37 | - name: Succeeded 38 | type: string 39 | jsonPath: ".status.conditions[?(@.type==\"Succeeded\")].status" 40 | conversion: 41 | strategy: Webhook 42 | webhook: 43 | clientConfig: 44 | service: 45 | name: kpack-webhook 46 | namespace: kpack 47 | path: /convert 48 | port: 443 49 | conversionReviewVersions: [ "v1" ] 50 | names: 51 | kind: Build 52 | listKind: BuildList 53 | singular: build 54 | plural: builds 55 | shortNames: 56 | - cnbbuild 57 | - cnbbuilds 58 | - bld 59 | - blds 60 | categories: 61 | - kpack 62 | scope: Namespaced 63 | -------------------------------------------------------------------------------- /config/builder.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: builders.kpack.io 5 | spec: 6 | group: kpack.io 7 | versions: 8 | - name: v1alpha1 9 | served: true 10 | storage: false 11 | schema: 12 | openAPIV3Schema: 13 | type: object 14 | x-kubernetes-preserve-unknown-fields: true 15 | subresources: 16 | status: {} 17 | additionalPrinterColumns: 18 | - name: LatestImage 19 | type: string 20 | jsonPath: ".status.latestImage" 21 | - name: Ready 22 | type: string 23 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 24 | - name: v1alpha2 25 | served: true 26 | storage: true 27 | schema: 28 | openAPIV3Schema: 29 | type: object 30 | x-kubernetes-preserve-unknown-fields: true 31 | subresources: 32 | status: {} 33 | additionalPrinterColumns: 34 | - name: LatestImage 35 | type: string 36 | jsonPath: ".status.latestImage" 37 | - name: Ready 38 | type: string 39 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 40 | - name: UpToDate 41 | type: string 42 | jsonPath: ".status.conditions[?(@.type==\"UpToDate\")].status" 43 | conversion: 44 | strategy: Webhook 45 | webhook: 46 | clientConfig: 47 | service: 48 | name: kpack-webhook 49 | namespace: kpack 50 | path: /convert 51 | port: 443 52 | conversionReviewVersions: [ "v1" ] 53 | names: 54 | kind: Builder 55 | listKind: BuilderList 56 | singular: builder 57 | plural: builders 58 | shortNames: 59 | - bldr 60 | - bldrs 61 | categories: 62 | - kpack 63 | scope: Namespaced 64 | -------------------------------------------------------------------------------- /config/buildpack.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: buildpacks.kpack.io 5 | spec: 6 | group: kpack.io 7 | versions: 8 | - name: v1alpha2 9 | served: true 10 | storage: true 11 | schema: 12 | openAPIV3Schema: 13 | type: object 14 | x-kubernetes-preserve-unknown-fields: true 15 | subresources: 16 | status: {} 17 | additionalPrinterColumns: 18 | - name: Ready 19 | type: string 20 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 21 | names: 22 | kind: Buildpack 23 | listKind: BuildpackList 24 | singular: buildpack 25 | plural: buildpacks 26 | shortNames: 27 | - bp 28 | - bps 29 | categories: 30 | - kpack 31 | scope: Namespaced 32 | 33 | -------------------------------------------------------------------------------- /config/clusterbuilder.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: clusterbuilders.kpack.io 5 | spec: 6 | group: kpack.io 7 | versions: 8 | - name: v1alpha1 9 | served: true 10 | storage: false 11 | schema: 12 | openAPIV3Schema: 13 | type: object 14 | x-kubernetes-preserve-unknown-fields: true 15 | subresources: 16 | status: {} 17 | additionalPrinterColumns: 18 | - name: LatestImage 19 | type: string 20 | jsonPath: ".status.latestImage" 21 | - name: Ready 22 | type: string 23 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 24 | - name: v1alpha2 25 | served: true 26 | storage: true 27 | schema: 28 | openAPIV3Schema: 29 | type: object 30 | x-kubernetes-preserve-unknown-fields: true 31 | subresources: 32 | status: {} 33 | additionalPrinterColumns: 34 | - name: LatestImage 35 | type: string 36 | jsonPath: ".status.latestImage" 37 | - name: Ready 38 | type: string 39 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 40 | - name: UpToDate 41 | type: string 42 | jsonPath: ".status.conditions[?(@.type==\"UpToDate\")].status" 43 | names: 44 | kind: ClusterBuilder 45 | listKind: ClusterBuilderList 46 | singular: clusterbuilder 47 | plural: clusterbuilders 48 | shortNames: 49 | - clstbldr 50 | - clstbldrs 51 | categories: 52 | - kpack 53 | scope: Cluster 54 | -------------------------------------------------------------------------------- /config/clusterbuildpack.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: clusterbuildpacks.kpack.io 5 | spec: 6 | group: kpack.io 7 | versions: 8 | - name: v1alpha2 9 | served: true 10 | storage: true 11 | schema: 12 | openAPIV3Schema: 13 | type: object 14 | x-kubernetes-preserve-unknown-fields: true 15 | subresources: 16 | status: {} 17 | additionalPrinterColumns: 18 | - name: Ready 19 | type: string 20 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 21 | names: 22 | kind: ClusterBuildpack 23 | listKind: ClusterBuildpackList 24 | singular: clusterbuildpack 25 | plural: clusterbuildpacks 26 | shortNames: 27 | - clstbp 28 | - clstbps 29 | categories: 30 | - kpack 31 | scope: Cluster 32 | -------------------------------------------------------------------------------- /config/clusterlifecycle.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: clusterlifecycles.kpack.io 5 | spec: 6 | group: kpack.io 7 | versions: 8 | - name: v1alpha2 9 | served: true 10 | storage: true 11 | schema: 12 | openAPIV3Schema: 13 | type: object 14 | x-kubernetes-preserve-unknown-fields: true 15 | subresources: 16 | status: {} 17 | additionalPrinterColumns: 18 | - name: Ready 19 | type: string 20 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 21 | names: 22 | kind: ClusterLifecycle 23 | listKind: ClusterLifecycleList 24 | singular: clusterlifecycle 25 | plural: clusterlifecycles 26 | categories: 27 | - kpack 28 | scope: Cluster 29 | -------------------------------------------------------------------------------- /config/clusterstack.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: clusterstacks.kpack.io 5 | spec: 6 | group: kpack.io 7 | versions: 8 | - name: v1alpha1 9 | served: true 10 | storage: false 11 | schema: 12 | openAPIV3Schema: 13 | type: object 14 | x-kubernetes-preserve-unknown-fields: true 15 | subresources: 16 | status: {} 17 | additionalPrinterColumns: 18 | - name: Ready 19 | type: string 20 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 21 | - name: v1alpha2 22 | served: true 23 | storage: true 24 | schema: 25 | openAPIV3Schema: 26 | type: object 27 | x-kubernetes-preserve-unknown-fields: true 28 | subresources: 29 | status: {} 30 | additionalPrinterColumns: 31 | - name: Ready 32 | type: string 33 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 34 | names: 35 | kind: ClusterStack 36 | listKind: ClusterStackList 37 | singular: clusterstack 38 | plural: clusterstacks 39 | categories: 40 | - kpack 41 | scope: Cluster 42 | -------------------------------------------------------------------------------- /config/clusterstore.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: clusterstores.kpack.io 5 | spec: 6 | group: kpack.io 7 | versions: 8 | - name: v1alpha1 9 | served: true 10 | storage: false 11 | schema: 12 | openAPIV3Schema: 13 | type: object 14 | x-kubernetes-preserve-unknown-fields: true 15 | additionalPrinterColumns: 16 | - name: Ready 17 | type: string 18 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 19 | subresources: 20 | status: {} 21 | - name: v1alpha2 22 | served: true 23 | storage: true 24 | schema: 25 | openAPIV3Schema: 26 | type: object 27 | x-kubernetes-preserve-unknown-fields: true 28 | additionalPrinterColumns: 29 | - name: Ready 30 | type: string 31 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 32 | subresources: 33 | status: {} 34 | names: 35 | kind: ClusterStore 36 | listKind: ClusterStoreList 37 | singular: clusterstore 38 | plural: clusterstores 39 | categories: 40 | - kpack 41 | scope: Cluster 42 | -------------------------------------------------------------------------------- /config/config-logging.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | apiVersion: v1 5 | kind: ConfigMap 6 | metadata: 7 | name: config-logging 8 | namespace: kpack 9 | 10 | data: 11 | zap-logger-config: | 12 | { 13 | "level": "info", 14 | "development": false, 15 | "outputPaths": ["stdout"], 16 | "errorOutputPaths": ["stderr"], 17 | "encoding": "json", 18 | "encoderConfig": { 19 | "timeKey": "ts", 20 | "levelKey": "level", 21 | "nameKey": "logger", 22 | "callerKey": "caller", 23 | "messageKey": "msg", 24 | "stacktraceKey": "stacktrace", 25 | "lineEnding": "", 26 | "levelEncoder": "", 27 | "timeEncoder": "rfc3339nano", 28 | "durationEncoder": "", 29 | "callerEncoder": "" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /config/default_clusterlifecycle.yaml: -------------------------------------------------------------------------------- 1 | #@ load("@ytt:data", "data") 2 | 3 | apiVersion: kpack.io/v1alpha2 4 | kind: ClusterLifecycle 5 | metadata: 6 | name: default-lifecycle 7 | spec: 8 | image: #@ data.values.lifecycle.image 9 | -------------------------------------------------------------------------------- /config/image.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: images.kpack.io 5 | spec: 6 | group: kpack.io 7 | versions: 8 | - name: v1alpha1 9 | served: true 10 | storage: false 11 | schema: 12 | openAPIV3Schema: 13 | type: object 14 | x-kubernetes-preserve-unknown-fields: true 15 | subresources: 16 | status: {} 17 | additionalPrinterColumns: 18 | - name: LatestImage 19 | type: string 20 | jsonPath: ".status.latestImage" 21 | - name: Ready 22 | type: string 23 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 24 | - name: v1alpha2 25 | served: true 26 | storage: true 27 | schema: 28 | openAPIV3Schema: 29 | type: object 30 | x-kubernetes-preserve-unknown-fields: true 31 | subresources: 32 | status: {} 33 | additionalPrinterColumns: 34 | - name: LatestImage 35 | type: string 36 | jsonPath: ".status.latestImage" 37 | - name: Ready 38 | type: string 39 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 40 | conversion: 41 | strategy: Webhook 42 | webhook: 43 | clientConfig: 44 | service: 45 | name: kpack-webhook 46 | namespace: kpack 47 | path: /convert 48 | port: 443 49 | conversionReviewVersions: ["v1"] 50 | names: 51 | kind: Image 52 | listKind: ImageList 53 | singular: image 54 | plural: images 55 | shortNames: 56 | - cnbimage 57 | - cnbimages 58 | - img 59 | - imgs 60 | categories: 61 | - kpack 62 | scope: Namespaced 63 | -------------------------------------------------------------------------------- /config/priority.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: scheduling.k8s.io/v1 3 | kind: PriorityClass 4 | metadata: 5 | name: kpack-control-plane 6 | value: 10000 7 | globalDefault: false 8 | description: "Super High priority class for kpack control plane components" 9 | --- 10 | apiVersion: scheduling.k8s.io/v1 11 | kind: PriorityClass 12 | metadata: 13 | name: kpack-build-high-priority 14 | value: 1000 15 | globalDefault: false 16 | description: "High priority class for kpack builds triggered by user changes." 17 | --- 18 | apiVersion: scheduling.k8s.io/v1 19 | kind: PriorityClass 20 | metadata: 21 | name: kpack-build-low-priority 22 | value: 1 23 | globalDefault: false 24 | preemptionPolicy: Never 25 | description: "Low priority class for kpack builds triggered by operator changes." 26 | -------------------------------------------------------------------------------- /config/schema.yaml: -------------------------------------------------------------------------------- 1 | #@data/values-schema 2 | #@overlay/match-child-defaults missing_ok=True 3 | --- 4 | kpack_version: dev 5 | controller: 6 | image: controller 7 | webhook: 8 | image: webhook 9 | build_init: 10 | image: build-init 11 | build_waiter: 12 | image: build-waiter 13 | rebase: 14 | image: rebase 15 | completion: 16 | image: completion 17 | lifecycle: 18 | image: lifecycle 19 | -------------------------------------------------------------------------------- /config/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: kpack-webhook 5 | namespace: kpack 6 | spec: 7 | ports: 8 | - port: 443 9 | targetPort: 8443 10 | selector: 11 | role: webhook -------------------------------------------------------------------------------- /config/sourceresolver.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: sourceresolvers.kpack.io 5 | spec: 6 | group: kpack.io 7 | versions: 8 | - name: v1alpha1 9 | served: true 10 | storage: false 11 | schema: 12 | openAPIV3Schema: 13 | type: object 14 | x-kubernetes-preserve-unknown-fields: true 15 | subresources: 16 | status: {} 17 | additionalPrinterColumns: 18 | - name: Ready 19 | type: string 20 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 21 | - name: v1alpha2 22 | served: true 23 | storage: true 24 | schema: 25 | openAPIV3Schema: 26 | type: object 27 | x-kubernetes-preserve-unknown-fields: true 28 | subresources: 29 | status: {} 30 | additionalPrinterColumns: 31 | - name: Ready 32 | type: string 33 | jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" 34 | conversion: 35 | strategy: Webhook 36 | webhook: 37 | clientConfig: 38 | service: 39 | name: kpack-webhook 40 | namespace: kpack 41 | path: /convert 42 | port: 443 43 | conversionReviewVersions: [ "v1" ] 44 | names: 45 | kind: SourceResolver 46 | listKind: SourceResolverList 47 | singular: sourceresolver 48 | plural: sourceresolvers 49 | categories: 50 | - kpack 51 | scope: Namespaced 52 | -------------------------------------------------------------------------------- /docs/assets/k8s-components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/docs/assets/k8s-components.png -------------------------------------------------------------------------------- /docs/assets/k8s-control-loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/docs/assets/k8s-control-loop.png -------------------------------------------------------------------------------- /docs/assets/kpack-big-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/docs/assets/kpack-big-white.png -------------------------------------------------------------------------------- /docs/assets/kpack-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/docs/assets/kpack-big.png -------------------------------------------------------------------------------- /docs/assets/kpack-control-loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/docs/assets/kpack-control-loop.png -------------------------------------------------------------------------------- /docs/assets/kpack-high-level.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/docs/assets/kpack-high-level.jpg -------------------------------------------------------------------------------- /docs/assets/kpack-stack-change.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/docs/assets/kpack-stack-change.jpg -------------------------------------------------------------------------------- /docs/assets/kpack-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/docs/assets/kpack-white.png -------------------------------------------------------------------------------- /docs/assets/kpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/docs/assets/kpack.png -------------------------------------------------------------------------------- /docs/assets/node-min.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/docs/assets/node-min.gif -------------------------------------------------------------------------------- /docs/bump-build-api.md: -------------------------------------------------------------------------------- 1 | # Bumping the build API version 2 | 3 | As an example, we show how to update from `v1alpha1` to `v1alpha2` 4 | 5 | 1. Copy `./pkg/api/build/v1alpha1` to `./pkg/api/build/v1alpha2` 6 | 1. Set package to `v1alpha2` in `./pkg/api/build/v1alpha2` 7 | 1. Update `./pkg/api/build/v1alpha2/register.go` to include 8 | 9 | ```go 10 | var SchemeGroupVersion = schema.GroupVersion{Group: build.GroupName, Version: "v1alpha2"} 11 | ``` 12 | 13 | 1. Remove now outdated methods and tests from `v1alpha1` 14 | 1. Search and replace: 15 | ``` 16 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha1" -> buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 17 | ``` 18 | 1. Update `./hack/update-codegen.sh` 19 | Append `v1alpha2` to group: `deepcopy,client,informer,lister` 20 | ```bash 21 | bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,informer,lister" \ 22 | github.com/pivotal/kpack/pkg/client github.com/pivotal/kpack/pkg/apis \ 23 | "build:v1alpha1,v1alpha2" \ 24 | --output-base "${TMP_DIR}/src" \ 25 | --go-header-file "${SCRIPT_ROOT}"/hack/boilerplate/boilerplate.go.txt 26 | ``` 27 | 1. Update `./hack/openapi-codegen.sh` to include `v1alpha2` 28 | ```bash 29 | ${OPENAPI_GEN_BIN} \ 30 | -h ./hack/boilerplate/boilerplate.go.txt \ 31 | -i github.com/pivotal/kpack/pkg/apis/build/v1alpha2,github.com/pivotal/kpack/pkg/apis/core/v1alpha1 \ 32 | -p ./pkg/openapi \ 33 | -o ./ 34 | 35 | # VolatileTime has custom json encoding/decoding that does not map to a proper json schema. Use a basic string instead. 36 | sed -i.old 's/Ref\: ref(\"github.com\/pivotal\/kpack\/pkg\/apis\/core\/v1alpha2.VolatileTime\"),/Type: []string{\"string\"}, Format: \"\",/g' pkg/openapi/openapi_generated.go 37 | ``` 38 | 1. Run `./hack/openapi-codegen.sh` and `./hack/update-codegen.sh` 39 | 1. Replace all occurrences of `Kpack().V1alpha1()` with `Kpack().V1alpha2()` (!*NOT* in `generic.go`) 40 | 1. Replace all occurrences of `KpackV1alpha2()` with `KpackV1alpha2()` (!*NOT* in `pkg/client`) 41 | 1. Adapt `config/*.yaml` 42 | * `v1alpha2` 43 | * `served` 44 | * `storage` 45 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | # Installing kpack 2 | 3 | ## Prerequisites 4 | 5 | 1. A Kubernetes cluster version 1.22 or later 6 | 1. [kubectl CLI](https://kubernetes.io/docs/tasks/tools/install-kubectl/) 7 | 1. Cluster-admin permissions for the current user 8 | 1. Accessible Docker V2 Registry 9 | 10 | ## Installing-kpack 11 | 12 | 1. Download the most recent [github release](https://github.com/pivotal/kpack/releases). The release.yaml is an asset on the release. 13 | 14 | ```bash 15 | kubectl apply --filename release-.yaml 16 | ``` 17 | 18 | 1. Ensure that the kpack controller & webhook have a status of `Running` using `kubectl get`. 19 | 20 | ```bash 21 | kubectl get pods --namespace kpack --watch 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /docs/logs.md: -------------------------------------------------------------------------------- 1 | # kpack logs 2 | 3 | Logs can be read with the `kp build logs` command [here](https://github.com/vmware-tanzu/kpack-cli/blob/main/docs/kp_build_logs.md). 4 | -------------------------------------------------------------------------------- /docs/stack.md: -------------------------------------------------------------------------------- 1 | # Stacks 2 | 3 | A stack resource is the specification for a [cloud native buildpacks stack](https://buildpacks.io/docs/concepts/components/stack/) used during build and in the resulting app image. 4 | 5 | The stack will be referenced by a [builder](builders.md) resource. 6 | 7 | At this time only a Cluster scoped `ClusterStack` is available. 8 | 9 | Corresponding `kp` cli command docs [here](https://github.com/vmware-tanzu/kpack-cli/blob/main/docs/kp_clusterstack.md). 10 | 11 | ### Cluster Stack Configuration 12 | 13 | ```yaml 14 | apiVersion: kpack.io/v1alpha2 15 | kind: ClusterStack 16 | metadata: 17 | name: base 18 | spec: 19 | id: "io.buildpacks.stacks.jammy" 20 | buildImage: 21 | image: "paketobuildpacks/build-jammy-base" 22 | runImage: 23 | image: "paketobuildpacks/run-jammy-base" 24 | ``` 25 | 26 | * `id`: The 'id' of the stack 27 | * `buildImage.image`: The build image of stack. 28 | * `runImage.image`: The run image of stack. 29 | 30 | ### Using a private registry 31 | 32 | To use stack images from a private registry, you have to add a `serviceAccountRef` referencing a serviceaccount with the secrets needed to pull from this registry. 33 | 34 | ```yaml 35 | spec: 36 | serviceAccountRef: 37 | name: private 38 | namespace: private 39 | ``` 40 | 41 | * `serviceAccountRef`: An object reference to a service account in any namespace. The object reference must contain `name` and `namespace`. 42 | 43 | ### Updating a stack 44 | 45 | The stack resource will not poll for updates. A CI/CD tool is needed to update the resource with new digests when new stack images are available. 46 | 47 | ### Suggested stacks 48 | 49 | The [pack CLI](https://github.com/buildpacks/pack) command: `pack stack suggest` will display a list of recommended stacks that can be used. We recommend starting with the `io.buildpacks.stacks.jammy` base stack. 50 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.sh.txt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 The original author or authors 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/deploytilt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sed -i -e 's/^/#/' config/config-logging.yaml 4 | 5 | export KPACK_lifecycle__image="gcr.io/cf-build-service-public/kpack/lifecycle" 6 | 7 | ytt -f config/ --data-values-env-yaml KPACK 8 | -------------------------------------------------------------------------------- /hack/openapi-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | ROOT=$(realpath $(dirname ${BASH_SOURCE})/..) 8 | 9 | cd "$ROOT" 10 | 11 | go run k8s.io/kube-openapi/cmd/openapi-gen \ 12 | --go-header-file ./hack/boilerplate/boilerplate.go.txt \ 13 | --output-pkg openapi \ 14 | --output-dir ./pkg/openapi \ 15 | github.com/pivotal/kpack/pkg/apis/build/v1alpha1 github.com/pivotal/kpack/pkg/apis/build/v1alpha2 github.com/pivotal/kpack/pkg/apis/core/v1alpha1 16 | 17 | # VolatileTime has custom json encoding/decoding that does not map to a proper json schema. Use a basic string instead. 18 | sed -i.old 's/Ref\: ref(\"github.com\/pivotal\/kpack\/pkg\/apis\/core\/v1alpha1.VolatileTime\"),/Type: []string{\"string\"}, Format: \"\",/g' pkg/openapi/generated.openapi.go 19 | sed -i.old 's/Ref\: ref(\"github.com\/pivotal\/kpack\/pkg\/apis\/core\/v1alpha2.VolatileTime\"),/Type: []string{\"string\"}, Format: \"\",/g' pkg/openapi/generated.openapi.go 20 | 21 | go run ./hack/openapi/main.go 1> ./api/openapi-spec/swagger.json 22 | rm -f pkg/openapi/generated.openapi.go.old 23 | 24 | cd - 25 | -------------------------------------------------------------------------------- /hack/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | 3 | // This package imports things required by build scripts, to force `go mod` to see them as dependencies 4 | package tools 5 | 6 | import _ "k8s.io/code-generator" 7 | -------------------------------------------------------------------------------- /hack/update-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | SCRIPT_ROOT=$(realpath $(dirname ${BASH_SOURCE})/..) 8 | 9 | CODEGEN_PKG=$(go list -m -mod=readonly -f "{{.Dir}}" k8s.io/code-generator) 10 | source "${CODEGEN_PKG}"/kube_codegen.sh 11 | 12 | kube::codegen::gen_helpers \ 13 | --boilerplate "${SCRIPT_ROOT}"/hack/boilerplate/boilerplate.go.txt \ 14 | "${SCRIPT_ROOT}/pkg/apis" 15 | 16 | kube::codegen::gen_client \ 17 | --boilerplate "${SCRIPT_ROOT}"/hack/boilerplate/boilerplate.go.txt \ 18 | --output-dir "${SCRIPT_ROOT}/pkg/client" \ 19 | --output-pkg "github.com/pivotal/kpack/pkg/client" \ 20 | --with-watch \ 21 | "${SCRIPT_ROOT}/pkg/apis" 22 | 23 | go mod tidy 24 | -------------------------------------------------------------------------------- /internal/logrus/fatal/fatal_level.go: -------------------------------------------------------------------------------- 1 | package fatal 2 | 3 | import "github.com/sirupsen/logrus" 4 | 5 | // package can be imported to set the log level to fatal 6 | 7 | func init() { 8 | logrus.SetLevel(logrus.FatalLevel) 9 | } 10 | -------------------------------------------------------------------------------- /pkg/apis/build/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package build 19 | 20 | const ( 21 | GroupName = "kpack.io" 22 | ) 23 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/build_lifecycle.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 5 | corev1 "k8s.io/api/core/v1" 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | ) 8 | 9 | func (bs *BuildStatus) Error(err error) { 10 | bs.Conditions = corev1alpha1.Conditions{ 11 | { 12 | LastTransitionTime: corev1alpha1.VolatileTime{Inner: metav1.Now()}, 13 | Type: corev1alpha1.ConditionSucceeded, 14 | Status: corev1.ConditionFalse, 15 | Message: err.Error(), 16 | }, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/build_validation.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "context" 5 | 6 | "knative.dev/pkg/apis" 7 | "knative.dev/pkg/kmp" 8 | 9 | "github.com/pivotal/kpack/pkg/apis/validate" 10 | ) 11 | 12 | func (b *Build) SetDefaults(ctx context.Context) { 13 | if b.Spec.ServiceAccount == "" { 14 | b.Spec.ServiceAccount = "default" 15 | } 16 | } 17 | 18 | func (b *Build) Validate(ctx context.Context) *apis.FieldError { 19 | return b.Spec.Validate(ctx).ViaField("spec") 20 | } 21 | 22 | func (bs *BuildSpec) Validate(ctx context.Context) *apis.FieldError { 23 | return validate.ListNotEmpty(bs.Tags, "tags"). 24 | Also(validate.Tags(bs.Tags, "tags")). 25 | Also(bs.Builder.Validate(ctx).ViaField("builder")). 26 | Also(bs.Source.Validate(ctx).ViaField("source")). 27 | Also(bs.Bindings.Validate(ctx).ViaField("bindings")). 28 | Also(bs.LastBuild.Validate(ctx).ViaField("lastBuild")). 29 | Also(bs.validateImmutableFields(ctx)) 30 | } 31 | 32 | func (bs *BuildSpec) validateImmutableFields(ctx context.Context) *apis.FieldError { 33 | if !apis.IsInUpdate(ctx) { 34 | return nil 35 | } 36 | 37 | original := apis.GetBaseline(ctx).(*Build) 38 | if diff, err := kmp.ShortDiff(&original.Spec, bs); err != nil { 39 | return &apis.FieldError{ 40 | Message: "Failed to diff Build", 41 | Paths: []string{"spec"}, 42 | Details: err.Error(), 43 | } 44 | } else if diff != "" { 45 | return &apis.FieldError{ 46 | Message: "Immutable fields changed (-old +new)", 47 | Paths: []string{"spec"}, 48 | Details: diff, 49 | } 50 | } 51 | return nil 52 | } 53 | 54 | func (lb *LastBuild) Validate(context context.Context) *apis.FieldError { 55 | if lb == nil || lb.Image == "" { 56 | return nil 57 | } 58 | 59 | return validate.Image(lb.Image) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/builder_lifecycle.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | 6 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | 8 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 9 | ) 10 | 11 | type BuilderRecord struct { 12 | Image string 13 | Stack corev1alpha1.BuildStack 14 | Buildpacks corev1alpha1.BuildpackMetadataList 15 | Order []corev1alpha1.OrderEntry 16 | ObservedStoreGeneration int64 17 | ObservedStackGeneration int64 18 | OS string 19 | } 20 | 21 | func (bs *BuilderStatus) BuilderRecord(record BuilderRecord) { 22 | bs.Stack = record.Stack 23 | bs.BuilderMetadata = record.Buildpacks 24 | bs.LatestImage = record.Image 25 | bs.Conditions = corev1alpha1.Conditions{ 26 | { 27 | LastTransitionTime: corev1alpha1.VolatileTime{Inner: v1.Now()}, 28 | Type: corev1alpha1.ConditionReady, 29 | Status: corev1.ConditionTrue, 30 | }, 31 | } 32 | bs.Order = record.Order 33 | bs.ObservedStoreGeneration = record.ObservedStoreGeneration 34 | bs.ObservedStackGeneration = record.ObservedStackGeneration 35 | bs.OS = record.OS 36 | } 37 | 38 | func (cb *BuilderStatus) ErrorCreate(err error) { 39 | cb.Status = corev1alpha1.Status{ 40 | Conditions: corev1alpha1.Conditions{ 41 | { 42 | Type: corev1alpha1.ConditionReady, 43 | Status: corev1.ConditionFalse, 44 | LastTransitionTime: corev1alpha1.VolatileTime{Inner: v1.Now()}, 45 | Message: err.Error(), 46 | }, 47 | }, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/builder_resource.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 4 | 5 | type BuilderResource interface { 6 | GetName() string 7 | BuildBuilderSpec() corev1alpha1.BuildBuilderSpec 8 | Ready() bool 9 | BuildpackMetadata() corev1alpha1.BuildpackMetadataList 10 | RunImage() string 11 | } 12 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/builder_validation.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "context" 5 | 6 | v1 "k8s.io/api/core/v1" 7 | 8 | "knative.dev/pkg/apis" 9 | 10 | "github.com/pivotal/kpack/pkg/apis/validate" 11 | ) 12 | 13 | func (cb *Builder) SetDefaults(context.Context) { 14 | if cb.Spec.ServiceAccount == "" { 15 | cb.Spec.ServiceAccount = "default" 16 | } 17 | if cb.Spec.Stack.Kind == "" { 18 | cb.Spec.Stack.Kind = ClusterStackKind 19 | } 20 | if cb.Spec.Store.Kind == "" { 21 | cb.Spec.Store.Kind = ClusterStoreKind 22 | } 23 | } 24 | 25 | func (cb *Builder) Validate(ctx context.Context) *apis.FieldError { 26 | return cb.Spec.Validate(ctx).ViaField("spec") 27 | } 28 | 29 | func (s *BuilderSpec) Validate(ctx context.Context) *apis.FieldError { 30 | return validate.Tag(s.Tag). 31 | Also(validateStack(s.Stack).ViaField("stack")). 32 | Also(validateStore(s.Store).ViaField("store")) 33 | } 34 | 35 | func (s *NamespacedBuilderSpec) Validate(ctx context.Context) *apis.FieldError { 36 | return s.BuilderSpec.Validate(ctx). 37 | Also(validate.FieldNotEmpty(s.ServiceAccount, "serviceAccount")) 38 | } 39 | 40 | func validateStack(stack v1.ObjectReference) *apis.FieldError { 41 | if stack.Name == "" { 42 | return apis.ErrMissingField("name") 43 | } 44 | 45 | switch stack.Kind { 46 | case ClusterStackKind: 47 | return nil 48 | default: 49 | return apis.ErrInvalidValue(stack.Kind, "kind") 50 | } 51 | } 52 | 53 | func validateStore(store v1.ObjectReference) *apis.FieldError { 54 | if store.Name == "" { 55 | return apis.ErrMissingField("name") 56 | } 57 | 58 | switch store.Kind { 59 | case ClusterStoreKind: 60 | return nil 61 | default: 62 | return apis.ErrInvalidValue(store.Kind, "kind") 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/cluster_builder_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | corev1 "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/apimachinery/pkg/runtime/schema" 10 | "k8s.io/apimachinery/pkg/types" 11 | "knative.dev/pkg/apis" 12 | ) 13 | 14 | const ClusterBuilderKind = "ClusterBuilder" 15 | 16 | // +genclient 17 | // +genclient:nonNamespaced 18 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object,k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMetaAccessor 19 | 20 | // +k8s:openapi-gen=true 21 | type ClusterBuilder struct { 22 | metav1.TypeMeta `json:",inline"` 23 | metav1.ObjectMeta `json:"metadata,omitempty"` 24 | 25 | Spec ClusterBuilderSpec `json:"spec"` 26 | Status BuilderStatus `json:"status"` 27 | } 28 | 29 | // +k8s:openapi-gen=true 30 | type ClusterBuilderSpec struct { 31 | BuilderSpec `json:",inline"` 32 | ServiceAccountRef corev1.ObjectReference `json:"serviceAccountRef,omitempty"` 33 | } 34 | 35 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 36 | 37 | // +k8s:openapi-gen=true 38 | type ClusterBuilderList struct { 39 | metav1.TypeMeta `json:",inline"` 40 | metav1.ListMeta `json:"metadata"` 41 | 42 | // +k8s:listType=atomic 43 | Items []ClusterBuilder `json:"items"` 44 | } 45 | 46 | func (*ClusterBuilder) GetGroupVersionKind() schema.GroupVersionKind { 47 | return SchemeGroupVersion.WithKind(ClusterBuilderKind) 48 | } 49 | 50 | func (c *ClusterBuilder) NamespacedName() types.NamespacedName { 51 | return types.NamespacedName{Namespace: c.Namespace, Name: c.Name} 52 | } 53 | 54 | func (c *ClusterBuilder) ConvertTo(_ context.Context, _ apis.Convertible) error { 55 | return errors.New("called convertTo in non-hub apiVersion v1alpha1") 56 | } 57 | 58 | func (c *ClusterBuilder) ConvertFrom(_ context.Context, _ apis.Convertible) error { 59 | return errors.New("called convertFrom in non-hub apiVersion v1alpha1") 60 | } 61 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/cluster_builder_validation.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "context" 5 | 6 | "knative.dev/pkg/apis" 7 | ) 8 | 9 | func (ccb *ClusterBuilder) SetDefaults(context.Context) { 10 | } 11 | 12 | func (ccb *ClusterBuilder) Validate(ctx context.Context) *apis.FieldError { 13 | return ccb.Spec.Validate(ctx) 14 | } 15 | 16 | func (ccbs *ClusterBuilderSpec) Validate(ctx context.Context) *apis.FieldError { 17 | if ccbs.ServiceAccountRef.Name == "" { 18 | return apis.ErrMissingField("name").ViaField("spec", "serviceAccountRef") 19 | } 20 | if ccbs.ServiceAccountRef.Namespace == "" { 21 | return apis.ErrMissingField("namespace").ViaField("spec", "serviceAccountRef") 22 | } 23 | return ccbs.BuilderSpec.Validate(ctx) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/cluster_builder_validation_test.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/sclevine/spec" 8 | "github.com/stretchr/testify/assert" 9 | corev1 "k8s.io/api/core/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "knative.dev/pkg/apis" 12 | ) 13 | 14 | func TestClusterBuilderValidation(t *testing.T) { 15 | spec.Run(t, "Cluster Builder Validation", testClusterBuilderValidation) 16 | } 17 | 18 | func testClusterBuilderValidation(t *testing.T, when spec.G, it spec.S) { 19 | clusterBuilder := &ClusterBuilder{ 20 | ObjectMeta: metav1.ObjectMeta{ 21 | Name: "custom-builder-name", 22 | Namespace: "custom-builder-namespace", 23 | }, 24 | Spec: ClusterBuilderSpec{ 25 | BuilderSpec: BuilderSpec{ 26 | Tag: "some-registry.io/custom-builder", 27 | Stack: corev1.ObjectReference{ 28 | Kind: "ClusterStack", 29 | Name: "some-stack-ref", 30 | }, 31 | Store: corev1.ObjectReference{ 32 | Kind: "ClusterStore", 33 | Name: "some-registry.io/store", 34 | }, 35 | Order: nil, // No order validation 36 | }, 37 | ServiceAccountRef: corev1.ObjectReference{ 38 | Name: "some-sa-name", 39 | Namespace: "some-sa-namespace", 40 | }, 41 | }, 42 | } 43 | 44 | when("Validate", func() { 45 | it("returns nil on no validation error", func() { 46 | assert.Nil(t, clusterBuilder.Validate(context.TODO())) 47 | }) 48 | 49 | assertValidationError := func(ccb *ClusterBuilder, expectedError *apis.FieldError) { 50 | t.Helper() 51 | err := ccb.Validate(context.TODO()) 52 | assert.EqualError(t, err, expectedError.Error()) 53 | } 54 | 55 | it("missing service account name", func() { 56 | clusterBuilder.Spec.ServiceAccountRef.Name = "" 57 | assertValidationError(clusterBuilder, apis.ErrMissingField("name").ViaField("spec", "serviceAccountRef")) 58 | }) 59 | 60 | it("missing service account namespace", func() { 61 | clusterBuilder.Spec.ServiceAccountRef.Namespace = "" 62 | assertValidationError(clusterBuilder, apis.ErrMissingField("namespace").ViaField("spec", "serviceAccountRef")) 63 | }) 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/cluster_stack_validation.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "context" 5 | 6 | "knative.dev/pkg/apis" 7 | 8 | "github.com/pivotal/kpack/pkg/apis/validate" 9 | ) 10 | 11 | func (s *ClusterStack) SetDefaults(context.Context) { 12 | } 13 | 14 | func (s *ClusterStack) Validate(ctx context.Context) *apis.FieldError { 15 | return s.Spec.Validate(ctx).ViaField("spec") 16 | } 17 | 18 | func (ss *ClusterStackSpec) Validate(ctx context.Context) *apis.FieldError { 19 | return validate.FieldNotEmpty(ss.Id, "id"). 20 | Also(ss.BuildImage.Validate(ctx).ViaField("buildImage")). 21 | Also(ss.RunImage.Validate(ctx).ViaField("runImage")) 22 | } 23 | 24 | func (ssi *ClusterStackSpecImage) Validate(context.Context) *apis.FieldError { 25 | return validate.Image(ssi.Image) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/cluster_store_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/runtime/schema" 9 | "knative.dev/pkg/apis" 10 | 11 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 12 | ) 13 | 14 | const ClusterStoreKind = "ClusterStore" 15 | 16 | // +genclient 17 | // +genclient:nonNamespaced 18 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object,k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMetaAccessor 19 | 20 | // +k8s:openapi-gen=true 21 | type ClusterStore struct { 22 | metav1.TypeMeta `json:",inline"` 23 | metav1.ObjectMeta `json:"metadata,omitempty"` 24 | 25 | Spec ClusterStoreSpec `json:"spec"` 26 | Status ClusterStoreStatus `json:"status"` 27 | } 28 | 29 | // +k8s:openapi-gen=true 30 | type ClusterStoreSpec struct { 31 | // +listType 32 | Sources []corev1alpha1.ImageSource `json:"sources,omitempty"` 33 | } 34 | 35 | // +k8s:openapi-gen=true 36 | type ClusterStoreStatus struct { 37 | corev1alpha1.Status `json:",inline"` 38 | 39 | // +listType 40 | Buildpacks []corev1alpha1.BuildpackStatus `json:"buildpacks,omitempty"` 41 | } 42 | 43 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 44 | 45 | // +k8s:openapi-gen=true 46 | type ClusterStoreList struct { 47 | metav1.TypeMeta `json:",inline"` 48 | metav1.ListMeta `json:"metadata"` 49 | 50 | // +k8s:listType=atomic 51 | Items []ClusterStore `json:"items"` 52 | } 53 | 54 | func (*ClusterStore) GetGroupVersionKind() schema.GroupVersionKind { 55 | return SchemeGroupVersion.WithKind(ClusterStoreKind) 56 | } 57 | 58 | func (s *ClusterStore) ConvertTo(_ context.Context, _ apis.Convertible) error { 59 | return errors.New("called convertTo in non-hub apiVersion v1alpha1") 60 | } 61 | 62 | func (s *ClusterStore) ConvertFrom(_ context.Context, _ apis.Convertible) error { 63 | return errors.New("called convertFrom in non-hub apiVersion v1alpha1") 64 | } 65 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/cluster_store_validation.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/google/go-containerregistry/pkg/name" 7 | "knative.dev/pkg/apis" 8 | ) 9 | 10 | func (s *ClusterStore) SetDefaults(context.Context) { 11 | } 12 | 13 | func (s *ClusterStore) Validate(ctx context.Context) *apis.FieldError { 14 | return s.Spec.Validate(ctx).ViaField("spec") 15 | } 16 | 17 | func (s *ClusterStoreSpec) Validate(ctx context.Context) *apis.FieldError { 18 | if len(s.Sources) == 0 { 19 | return apis.ErrMissingField("sources") 20 | } 21 | var errors *apis.FieldError = nil 22 | for i, source := range s.Sources { 23 | _, err := name.ParseReference(source.Image, name.WeakValidation) 24 | if err != nil { 25 | //noinspection GoNilness 26 | errors = errors.Also(apis.ErrInvalidArrayValue(source, "sources", i)) 27 | } 28 | } 29 | return errors 30 | } 31 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/cluster_store_validation_test.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/sclevine/spec" 8 | "github.com/stretchr/testify/assert" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "knative.dev/pkg/apis" 11 | 12 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 13 | ) 14 | 15 | func TestClusterStoreValidation(t *testing.T) { 16 | spec.Run(t, "ClusterStore Validation", testClusterStoreValidation) 17 | } 18 | 19 | func testClusterStoreValidation(t *testing.T, when spec.G, it spec.S) { 20 | clusterStore := &ClusterStore{ 21 | ObjectMeta: metav1.ObjectMeta{ 22 | Name: "store-name", 23 | }, 24 | Spec: ClusterStoreSpec{ 25 | Sources: []corev1alpha1.ImageSource{ 26 | { 27 | Image: "some-registry.io/store-image-1@sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d", 28 | }, 29 | { 30 | Image: "some-registry.io/store-image-2@sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d", 31 | }, 32 | { 33 | Image: "some-registry.io/store-image-3@sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d", 34 | }, 35 | }, 36 | }, 37 | } 38 | 39 | when("Validate", func() { 40 | it("returns nil on no validation error", func() { 41 | assert.Nil(t, clusterStore.Validate(context.TODO())) 42 | }) 43 | 44 | assertValidationError := func(clusterStore *ClusterStore, expectedError *apis.FieldError) { 45 | t.Helper() 46 | err := clusterStore.Validate(context.TODO()) 47 | assert.EqualError(t, err, expectedError.Error()) 48 | } 49 | 50 | it("missing field sources", func() { 51 | clusterStore.Spec.Sources = nil 52 | assertValidationError(clusterStore, apis.ErrMissingField("sources").ViaField("spec")) 53 | }) 54 | 55 | it("sources should contain a valid image", func() { 56 | clusterStore.Spec.Sources = append(clusterStore.Spec.Sources, corev1alpha1.ImageSource{Image: "invalid image"}) 57 | assertValidationError(clusterStore, apis.ErrInvalidArrayValue(clusterStore.Spec.Sources[3], "sources", 3).ViaField("spec")) 58 | }) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // +k8s:deepcopy-gen=package 18 | // +groupName=kpack.io 19 | 20 | package v1alpha1 21 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/image_lifecycle.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "fmt" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | 9 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 10 | ) 11 | 12 | const ( 13 | BuilderNotFound = "BuilderNotFound" 14 | BuilderNotReady = "BuilderNotReady" 15 | ) 16 | 17 | func (im *Image) BuilderNotFound() corev1alpha1.Conditions { 18 | return corev1alpha1.Conditions{ 19 | { 20 | Type: corev1alpha1.ConditionReady, 21 | Status: corev1.ConditionFalse, 22 | Reason: BuilderNotFound, 23 | Message: fmt.Sprintf("Unable to find builder %s.", im.Spec.Builder.Name), 24 | LastTransitionTime: corev1alpha1.VolatileTime{Inner: metav1.Now()}, 25 | }, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/source_resolver.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | 6 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 7 | ) 8 | 9 | const ActivePolling = "ActivePolling" 10 | 11 | func (sr *SourceResolver) ResolvedSource(config corev1alpha1.ResolvedSourceConfig) { 12 | resolvedSource := config.ResolvedSource() 13 | 14 | if resolvedSource.IsUnknown() && sr.Status.ObservedGeneration == sr.ObjectMeta.Generation { 15 | return 16 | } 17 | 18 | sr.Status.Source = config 19 | 20 | sr.Status.Conditions = []corev1alpha1.Condition{{ 21 | Type: corev1alpha1.ConditionReady, 22 | Status: corev1.ConditionTrue, 23 | }} 24 | 25 | pollingStatus := corev1.ConditionFalse 26 | if resolvedSource.IsPollable() { 27 | pollingStatus = corev1.ConditionTrue 28 | } 29 | sr.Status.Conditions = append(sr.Status.Conditions, corev1alpha1.Condition{ 30 | Type: ActivePolling, 31 | Status: pollingStatus, 32 | }) 33 | } 34 | 35 | func (sr *SourceResolver) PollingReady() bool { 36 | return sr.Status.GetCondition(ActivePolling).IsTrue() 37 | } 38 | 39 | func (sr *SourceResolver) Ready() bool { 40 | return sr.Status.GetCondition(corev1alpha1.ConditionReady).IsTrue() && 41 | (sr.Generation == sr.Status.ObservedGeneration) 42 | } 43 | 44 | func (sr SourceResolver) IsGit() bool { 45 | return sr.Spec.Source.Git != nil 46 | } 47 | 48 | func (sr SourceResolver) IsBlob() bool { 49 | return sr.Spec.Source.Blob != nil 50 | } 51 | 52 | func (sr SourceResolver) IsRegistry() bool { 53 | return sr.Spec.Source.Registry != nil 54 | } 55 | 56 | func (st *SourceResolver) SourceConfig() corev1alpha1.SourceConfig { 57 | return st.Status.Source.ResolvedSource().SourceConfig() 58 | } 59 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha1/source_resolver_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/runtime/schema" 9 | "knative.dev/pkg/apis" 10 | 11 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 12 | ) 13 | 14 | // +genclient 15 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 16 | 17 | // +k8s:openapi-gen=true 18 | type SourceResolver struct { 19 | metav1.TypeMeta `json:",inline"` 20 | metav1.ObjectMeta `json:"metadata,omitempty"` 21 | Spec SourceResolverSpec `json:"spec"` 22 | Status SourceResolverStatus `json:"status,omitempty"` 23 | } 24 | 25 | // +k8s:openapi-gen=true 26 | type SourceResolverSpec struct { 27 | ServiceAccount string `json:"serviceAccount,omitempty"` 28 | Source corev1alpha1.SourceConfig `json:"source"` 29 | } 30 | 31 | // +k8s:openapi-gen=true 32 | type SourceResolverStatus struct { 33 | corev1alpha1.Status `json:",inline"` 34 | Source corev1alpha1.ResolvedSourceConfig `json:"source,omitempty"` 35 | } 36 | 37 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 38 | 39 | // +k8s:openapi-gen=true 40 | type SourceResolverList struct { 41 | metav1.TypeMeta `json:",inline"` 42 | metav1.ListMeta `json:"metadata"` 43 | 44 | // +k8s:listType=atomic 45 | Items []SourceResolver `json:"items"` 46 | } 47 | 48 | func (*SourceResolver) GetGroupVersionKind() schema.GroupVersionKind { 49 | return SchemeGroupVersion.WithKind("SourceResolver") 50 | } 51 | 52 | func (i *SourceResolver) ConvertTo(_ context.Context, _ apis.Convertible) error { 53 | return errors.New("called convertTo in non-hub apiVersion v1alpha1") 54 | } 55 | 56 | func (i *SourceResolver) ConvertFrom(_ context.Context, _ apis.Convertible) error { 57 | return errors.New("called convertFrom in non-hub apiVersion v1alpha1") 58 | } 59 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/build_lifecycle.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 5 | corev1 "k8s.io/api/core/v1" 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | ) 8 | 9 | func (bs *BuildStatus) Error(err error) { 10 | bs.Conditions = corev1alpha1.Conditions{ 11 | { 12 | LastTransitionTime: corev1alpha1.VolatileTime{Inner: metav1.Now()}, 13 | Type: corev1alpha1.ConditionSucceeded, 14 | Status: corev1.ConditionFalse, 15 | Message: err.Error(), 16 | }, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/build_priority.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | type BuildPriority int 4 | 5 | var ( 6 | BuildPriorityNone = BuildPriority(0) 7 | BuildPriorityLow = BuildPriority(1) 8 | BuildPriorityHigh = BuildPriority(1000) 9 | BuildPriorityClassHigh = "kpack-build-high-priority" 10 | BuildPriorityClassLow = "kpack-build-low-priority" 11 | ) 12 | 13 | var PriorityClasses = map[BuildPriority]string{ 14 | BuildPriorityNone: "", 15 | BuildPriorityLow: BuildPriorityClassLow, 16 | BuildPriorityHigh: BuildPriorityClassHigh, 17 | } 18 | 19 | func (p BuildPriority) PriorityClass() string { 20 | return PriorityClasses[p] 21 | } 22 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/builder_resource.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 4 | 5 | type BuilderResource interface { 6 | GetName() string 7 | GetNamespace() string 8 | BuildBuilderSpec() corev1alpha1.BuildBuilderSpec 9 | Ready() bool 10 | UpToDate() bool 11 | BuildpackMetadata() corev1alpha1.BuildpackMetadataList 12 | RunImage() string 13 | LifecycleVersion() string 14 | GetKind() string 15 | ConditionReadyMessage() string 16 | } 17 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/buildpack_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | "k8s.io/apimachinery/pkg/types" 7 | 8 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 9 | ) 10 | 11 | const ( 12 | BuildpackKind = "Buildpack" 13 | BuildpackCRName = "buildpacks.kpack.io" 14 | ) 15 | 16 | // +genclient 17 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object,k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMetaAccessor 18 | 19 | // +k8s:openapi-gen=true 20 | type Buildpack struct { 21 | metav1.TypeMeta `json:",inline"` 22 | metav1.ObjectMeta `json:"metadata,omitempty"` 23 | 24 | Spec BuildpackSpec `json:"spec"` 25 | Status BuildpackStatus `json:"status"` 26 | } 27 | 28 | // +k8s:openapi-gen=true 29 | type BuildpackSpec struct { 30 | // +listType 31 | corev1alpha1.ImageSource `json:",inline"` 32 | ServiceAccountName string `json:"serviceAccountName,omitempty"` 33 | } 34 | 35 | // +k8s:openapi-gen=true 36 | type BuildpackStatus struct { 37 | corev1alpha1.Status `json:",inline"` 38 | 39 | // +listType 40 | Buildpacks []corev1alpha1.BuildpackStatus `json:"buildpacks,omitempty"` 41 | } 42 | 43 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 44 | 45 | // +k8s:openapi-gen=true 46 | type BuildpackList struct { 47 | metav1.TypeMeta `json:",inline"` 48 | metav1.ListMeta `json:"metadata"` 49 | 50 | // +k8s:listType=atomic 51 | Items []Buildpack `json:"items"` 52 | } 53 | 54 | func (*Buildpack) GetGroupVersionKind() schema.GroupVersionKind { 55 | return SchemeGroupVersion.WithKind(BuildpackKind) 56 | } 57 | 58 | func (c *Buildpack) NamespacedName() types.NamespacedName { 59 | return types.NamespacedName{Namespace: c.Namespace, Name: c.Name} 60 | } 61 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/buildpack_validation.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pivotal/kpack/pkg/apis/validate" 7 | "knative.dev/pkg/apis" 8 | ) 9 | 10 | func (cb *Buildpack) SetDefaults(context.Context) { 11 | if cb.Spec.ServiceAccountName == "" { 12 | cb.Spec.ServiceAccountName = "default" 13 | } 14 | } 15 | 16 | func (cb *Buildpack) Validate(ctx context.Context) *apis.FieldError { 17 | return cb.Spec.Validate(ctx).ViaField("spec") 18 | } 19 | 20 | func (s *BuildpackSpec) Validate(ctx context.Context) *apis.FieldError { 21 | return validate.Image(s.Image) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/cluster_builder_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | "k8s.io/apimachinery/pkg/types" 8 | ) 9 | 10 | const ( 11 | ClusterBuilderKind = "ClusterBuilder" 12 | ClusterBuilderCRName = "clusterbuilders.kpack.io" 13 | ) 14 | 15 | // +genclient 16 | // +genclient:nonNamespaced 17 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object,k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMetaAccessor 18 | 19 | // +k8s:openapi-gen=true 20 | type ClusterBuilder struct { 21 | metav1.TypeMeta `json:",inline"` 22 | metav1.ObjectMeta `json:"metadata,omitempty"` 23 | 24 | Spec ClusterBuilderSpec `json:"spec"` 25 | Status BuilderStatus `json:"status"` 26 | } 27 | 28 | // +k8s:openapi-gen=true 29 | type ClusterBuilderSpec struct { 30 | BuilderSpec `json:",inline"` 31 | ServiceAccountRef corev1.ObjectReference `json:"serviceAccountRef,omitempty"` 32 | } 33 | 34 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 35 | 36 | // +k8s:openapi-gen=true 37 | type ClusterBuilderList struct { 38 | metav1.TypeMeta `json:",inline"` 39 | metav1.ListMeta `json:"metadata"` 40 | 41 | // +k8s:listType=atomic 42 | Items []ClusterBuilder `json:"items"` 43 | } 44 | 45 | func (*ClusterBuilder) GetGroupVersionKind() schema.GroupVersionKind { 46 | return SchemeGroupVersion.WithKind(ClusterBuilderKind) 47 | } 48 | 49 | func (c *ClusterBuilder) NamespacedName() types.NamespacedName { 50 | return types.NamespacedName{Namespace: c.Namespace, Name: c.Name} 51 | } 52 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/cluster_builder_validation.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | "context" 5 | 6 | "knative.dev/pkg/apis" 7 | ) 8 | 9 | func (ccb *ClusterBuilder) SetDefaults(context.Context) { 10 | if ccb.Spec.Lifecycle.Name == "" { 11 | ccb.Spec.Lifecycle.Name = DefaultLifecycleName 12 | } 13 | if ccb.Spec.Lifecycle.Kind == "" { 14 | ccb.Spec.Lifecycle.Kind = ClusterLifecycleKind 15 | } 16 | } 17 | 18 | func (ccb *ClusterBuilder) Validate(ctx context.Context) *apis.FieldError { 19 | return ccb.Spec.Validate(ctx) 20 | } 21 | 22 | func (ccbs *ClusterBuilderSpec) Validate(ctx context.Context) *apis.FieldError { 23 | if ccbs.ServiceAccountRef.Name == "" { 24 | return apis.ErrMissingField("name").ViaField("spec", "serviceAccountRef") 25 | } 26 | if ccbs.ServiceAccountRef.Namespace == "" { 27 | return apis.ErrMissingField("namespace").ViaField("spec", "serviceAccountRef") 28 | } 29 | return ccbs.BuilderSpec.Validate(ctx) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/cluster_builder_validation_test.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/sclevine/spec" 8 | "github.com/stretchr/testify/assert" 9 | corev1 "k8s.io/api/core/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "knative.dev/pkg/apis" 12 | ) 13 | 14 | func TestClusterBuilderValidation(t *testing.T) { 15 | spec.Run(t, "Cluster Builder Validation", testClusterBuilderValidation) 16 | } 17 | 18 | func testClusterBuilderValidation(t *testing.T, when spec.G, it spec.S) { 19 | clusterBuilder := &ClusterBuilder{ 20 | ObjectMeta: metav1.ObjectMeta{ 21 | Name: "custom-builder-name", 22 | Namespace: "custom-builder-namespace", 23 | }, 24 | Spec: ClusterBuilderSpec{ 25 | BuilderSpec: BuilderSpec{ 26 | Tag: "some-registry.io/custom-builder", 27 | Stack: corev1.ObjectReference{ 28 | Kind: "ClusterStack", 29 | Name: "some-stack-ref", 30 | }, 31 | Store: corev1.ObjectReference{ 32 | Kind: "ClusterStore", 33 | Name: "some-registry.io/store", 34 | }, 35 | Order: nil, // No order validation 36 | }, 37 | ServiceAccountRef: corev1.ObjectReference{ 38 | Name: "some-sa-name", 39 | Namespace: "some-sa-namespace", 40 | }, 41 | }, 42 | } 43 | 44 | when("Validate", func() { 45 | it("returns nil on no validation error", func() { 46 | assert.Nil(t, clusterBuilder.Validate(context.TODO())) 47 | }) 48 | 49 | assertValidationError := func(ccb *ClusterBuilder, expectedError *apis.FieldError) { 50 | t.Helper() 51 | err := ccb.Validate(context.TODO()) 52 | assert.EqualError(t, err, expectedError.Error()) 53 | } 54 | 55 | it("missing service account name", func() { 56 | clusterBuilder.Spec.ServiceAccountRef.Name = "" 57 | assertValidationError(clusterBuilder, apis.ErrMissingField("name").ViaField("spec", "serviceAccountRef")) 58 | }) 59 | 60 | it("missing service account namespace", func() { 61 | clusterBuilder.Spec.ServiceAccountRef.Namespace = "" 62 | assertValidationError(clusterBuilder, apis.ErrMissingField("namespace").ViaField("spec", "serviceAccountRef")) 63 | }) 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/cluster_buildpack_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | 8 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 9 | ) 10 | 11 | const ( 12 | ClusterBuildpackKind = "ClusterBuildpack" 13 | ClusterBuildpackCRName = "clusterbuildpacks.kpack.io" 14 | ) 15 | 16 | // +genclient 17 | // +genclient:nonNamespaced 18 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object,k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMetaAccessor 19 | 20 | // +k8s:openapi-gen=true 21 | type ClusterBuildpack struct { 22 | metav1.TypeMeta `json:",inline"` 23 | metav1.ObjectMeta `json:"metadata,omitempty"` 24 | 25 | Spec ClusterBuildpackSpec `json:"spec"` 26 | Status ClusterBuildpackStatus `json:"status"` 27 | } 28 | 29 | // +k8s:openapi-gen=true 30 | type ClusterBuildpackSpec struct { 31 | // +listType 32 | corev1alpha1.ImageSource `json:",inline"` 33 | ServiceAccountRef *corev1.ObjectReference `json:"serviceAccountRef,omitempty"` 34 | } 35 | 36 | // +k8s:openapi-gen=true 37 | type ClusterBuildpackStatus struct { 38 | corev1alpha1.Status `json:",inline"` 39 | 40 | // +listType 41 | Buildpacks []corev1alpha1.BuildpackStatus `json:"buildpacks,omitempty"` 42 | } 43 | 44 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 45 | 46 | // +k8s:openapi-gen=true 47 | type ClusterBuildpackList struct { 48 | metav1.TypeMeta `json:",inline"` 49 | metav1.ListMeta `json:"metadata"` 50 | 51 | // +k8s:listType=atomic 52 | Items []ClusterBuildpack `json:"items"` 53 | } 54 | 55 | func (*ClusterBuildpack) GetGroupVersionKind() schema.GroupVersionKind { 56 | return SchemeGroupVersion.WithKind(ClusterBuildpackKind) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/cluster_buildpack_validation.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pivotal/kpack/pkg/apis/validate" 7 | "knative.dev/pkg/apis" 8 | ) 9 | 10 | func (s *ClusterBuildpack) SetDefaults(context.Context) { 11 | } 12 | 13 | func (s *ClusterBuildpack) Validate(ctx context.Context) *apis.FieldError { 14 | return s.Spec.Validate(ctx).ViaField("spec") 15 | } 16 | 17 | func (s *ClusterBuildpackSpec) Validate(ctx context.Context) *apis.FieldError { 18 | if s.ServiceAccountRef != nil { 19 | if s.ServiceAccountRef.Name == "" { 20 | return apis.ErrMissingField("name").ViaField("serviceAccountRef") 21 | } 22 | if s.ServiceAccountRef.Namespace == "" { 23 | return apis.ErrMissingField("namespace").ViaField("serviceAccountRef") 24 | } 25 | } 26 | 27 | return validate.Image(s.Image) 28 | } 29 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/cluster_lifecycle_validation.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | "context" 5 | 6 | "knative.dev/pkg/apis" 7 | 8 | "github.com/pivotal/kpack/pkg/apis/validate" 9 | ) 10 | 11 | func (cl *ClusterLifecycle) SetDefaults(context.Context) { 12 | } 13 | 14 | func (cl *ClusterLifecycle) Validate(ctx context.Context) *apis.FieldError { 15 | return cl.Spec.Validate(ctx).ViaField("spec") 16 | } 17 | 18 | func (cls *ClusterLifecycleSpec) Validate(ctx context.Context) *apis.FieldError { 19 | if cls.ServiceAccountRef != nil { 20 | if cls.ServiceAccountRef.Name == "" { 21 | return apis.ErrMissingField("name").ViaField("serviceAccountRef") 22 | } 23 | if cls.ServiceAccountRef.Namespace == "" { 24 | return apis.ErrMissingField("namespace").ViaField("serviceAccountRef") 25 | } 26 | } 27 | 28 | return validate.FieldNotEmpty(cls.Image, "image") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/cluster_stack_validation.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | "context" 5 | 6 | "knative.dev/pkg/apis" 7 | 8 | "github.com/pivotal/kpack/pkg/apis/validate" 9 | ) 10 | 11 | func (s *ClusterStack) SetDefaults(context.Context) { 12 | } 13 | 14 | func (s *ClusterStack) Validate(ctx context.Context) *apis.FieldError { 15 | return s.Spec.Validate(ctx).ViaField("spec") 16 | } 17 | 18 | func (ss *ClusterStackSpec) Validate(ctx context.Context) *apis.FieldError { 19 | if ss.ServiceAccountRef != nil { 20 | if ss.ServiceAccountRef.Name == "" { 21 | return apis.ErrMissingField("name").ViaField("serviceAccountRef") 22 | } 23 | if ss.ServiceAccountRef.Namespace == "" { 24 | return apis.ErrMissingField("namespace").ViaField("serviceAccountRef") 25 | } 26 | } 27 | 28 | return validate.FieldNotEmpty(ss.Id, "id"). 29 | Also(ss.BuildImage.Validate(ctx).ViaField("buildImage")). 30 | Also(ss.RunImage.Validate(ctx).ViaField("runImage")) 31 | } 32 | 33 | func (ssi *ClusterStackSpecImage) Validate(context.Context) *apis.FieldError { 34 | return validate.Image(ssi.Image) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/cluster_store_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | 8 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 9 | ) 10 | 11 | const ( 12 | ClusterStoreKind = "ClusterStore" 13 | ClusterStoreCRName = "clusterstores.kpack.io" 14 | ) 15 | 16 | // +genclient 17 | // +genclient:nonNamespaced 18 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object,k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMetaAccessor 19 | 20 | // +k8s:openapi-gen=true 21 | type ClusterStore struct { 22 | metav1.TypeMeta `json:",inline"` 23 | metav1.ObjectMeta `json:"metadata,omitempty"` 24 | 25 | Spec ClusterStoreSpec `json:"spec"` 26 | Status ClusterStoreStatus `json:"status"` 27 | } 28 | 29 | // +k8s:openapi-gen=true 30 | type ClusterStoreSpec struct { 31 | // +listType 32 | Sources []corev1alpha1.ImageSource `json:"sources,omitempty"` 33 | ServiceAccountRef *corev1.ObjectReference `json:"serviceAccountRef,omitempty"` 34 | } 35 | 36 | // +k8s:openapi-gen=true 37 | type ClusterStoreStatus struct { 38 | corev1alpha1.Status `json:",inline"` 39 | 40 | // +listType 41 | Buildpacks []corev1alpha1.BuildpackStatus `json:"buildpacks,omitempty"` 42 | } 43 | 44 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 45 | 46 | // +k8s:openapi-gen=true 47 | type ClusterStoreList struct { 48 | metav1.TypeMeta `json:",inline"` 49 | metav1.ListMeta `json:"metadata"` 50 | 51 | // +k8s:listType=atomic 52 | Items []ClusterStore `json:"items"` 53 | } 54 | 55 | func (*ClusterStore) GetGroupVersionKind() schema.GroupVersionKind { 56 | return SchemeGroupVersion.WithKind(ClusterStoreKind) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/cluster_store_validation.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/google/go-containerregistry/pkg/name" 7 | "knative.dev/pkg/apis" 8 | ) 9 | 10 | func (s *ClusterStore) SetDefaults(context.Context) { 11 | } 12 | 13 | func (s *ClusterStore) Validate(ctx context.Context) *apis.FieldError { 14 | return s.Spec.Validate(ctx).ViaField("spec") 15 | } 16 | 17 | func (s *ClusterStoreSpec) Validate(ctx context.Context) *apis.FieldError { 18 | if s.ServiceAccountRef != nil { 19 | if s.ServiceAccountRef.Name == "" { 20 | return apis.ErrMissingField("name").ViaField("serviceAccountRef") 21 | } 22 | if s.ServiceAccountRef.Namespace == "" { 23 | return apis.ErrMissingField("namespace").ViaField("serviceAccountRef") 24 | } 25 | } 26 | 27 | if len(s.Sources) == 0 { 28 | return apis.ErrMissingField("sources") 29 | } 30 | var errors *apis.FieldError = nil 31 | for i, source := range s.Sources { 32 | _, err := name.ParseReference(source.Image, name.WeakValidation) 33 | if err != nil { 34 | //noinspection GoNilness 35 | errors = errors.Also(apis.ErrInvalidArrayValue(source, "sources", i)) 36 | } 37 | } 38 | return errors 39 | } 40 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/cosign_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | // +k8s:openapi-gen=true 4 | type CosignConfig struct { 5 | // +listType 6 | Annotations []CosignAnnotation `json:"annotations,omitempty"` 7 | } 8 | 9 | // +k8s:openapi-gen=true 10 | type CosignAnnotation struct { 11 | Name string `json:"name,omitempty"` 12 | Value string `json:"value,omitempty"` 13 | } 14 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/cosign_validation.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | "context" 5 | 6 | "knative.dev/pkg/apis" 7 | 8 | "github.com/pivotal/kpack/pkg/apis/validate" 9 | ) 10 | 11 | func (c *CosignConfig) Validate(ctx context.Context) *apis.FieldError { 12 | if c == nil { 13 | return nil 14 | } 15 | 16 | var err *apis.FieldError 17 | 18 | for i, item := range c.Annotations { 19 | err = err.Also(item.Validate(ctx).ViaIndex(i).ViaField("annotations")) 20 | } 21 | 22 | return err 23 | } 24 | 25 | func (c *CosignAnnotation) Validate(ctx context.Context) *apis.FieldError { 26 | if c == nil { 27 | return nil 28 | } 29 | 30 | return validate.FieldNotEmpty(c.Name, "name"). 31 | Also(validate.FieldNotEmpty(c.Value, "value")) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // +k8s:deepcopy-gen=package 18 | // +groupName=kpack.io 19 | 20 | package v1alpha2 21 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/image_lifecycle.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | "fmt" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | 9 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 10 | ) 11 | 12 | const ( 13 | BuilderNotFound = "BuilderNotFound" 14 | BuilderNotReady = "BuilderNotReady" 15 | BuilderReady = "BuilderReady" 16 | BuilderNotUpToDate = "BuilderNotUpToDate" 17 | BuilderUpToDate = "BuilderUpToDate" 18 | ) 19 | 20 | func (im *Image) BuilderNotFound() corev1alpha1.Conditions { 21 | return corev1alpha1.Conditions{ 22 | { 23 | Type: corev1alpha1.ConditionReady, 24 | Status: corev1.ConditionFalse, 25 | Reason: BuilderNotFound, 26 | Message: fmt.Sprintf("Error: Unable to find builder '%s' in namespace '%s'.", im.Spec.Builder.Name, im.Spec.Builder.Namespace), 27 | LastTransitionTime: corev1alpha1.VolatileTime{Inner: metav1.Now()}, 28 | }, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/source_resolver.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | 6 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 7 | ) 8 | 9 | const ActivePolling = "ActivePolling" 10 | 11 | func (sr *SourceResolver) ResolvedSource(config corev1alpha1.ResolvedSourceConfig) { 12 | resolvedSource := config.ResolvedSource() 13 | 14 | if resolvedSource.IsUnknown() && sr.Status.ObservedGeneration == sr.ObjectMeta.Generation { 15 | return 16 | } 17 | 18 | sr.Status.Source = config 19 | 20 | sr.Status.Conditions = []corev1alpha1.Condition{{ 21 | Type: corev1alpha1.ConditionReady, 22 | Status: corev1.ConditionTrue, 23 | }} 24 | 25 | pollingStatus := corev1.ConditionFalse 26 | if resolvedSource.IsPollable() { 27 | pollingStatus = corev1.ConditionTrue 28 | } 29 | sr.Status.Conditions = append(sr.Status.Conditions, corev1alpha1.Condition{ 30 | Type: ActivePolling, 31 | Status: pollingStatus, 32 | }) 33 | } 34 | 35 | func (sr *SourceResolver) PollingReady() bool { 36 | return sr.Status.GetCondition(ActivePolling).IsTrue() 37 | } 38 | 39 | func (sr *SourceResolver) Ready() bool { 40 | return sr.Status.GetCondition(corev1alpha1.ConditionReady).IsTrue() && 41 | (sr.Generation == sr.Status.ObservedGeneration) 42 | } 43 | 44 | func (sr SourceResolver) IsGit() bool { 45 | return sr.Spec.Source.Git != nil 46 | } 47 | 48 | func (sr SourceResolver) IsBlob() bool { 49 | return sr.Spec.Source.Blob != nil 50 | } 51 | 52 | func (sr SourceResolver) IsRegistry() bool { 53 | return sr.Spec.Source.Registry != nil 54 | } 55 | 56 | func (st *SourceResolver) SourceConfig() corev1alpha1.SourceConfig { 57 | return st.Status.Source.ResolvedSource().SourceConfig() 58 | } 59 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/source_resolver_conversion.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "knative.dev/pkg/apis" 8 | 9 | "github.com/pivotal/kpack/pkg/apis/build/v1alpha1" 10 | ) 11 | 12 | func (i *SourceResolver) ConvertTo(_ context.Context, to apis.Convertible) error { 13 | switch toSourceResolver := to.(type) { 14 | case *v1alpha1.SourceResolver: 15 | toSourceResolver.ObjectMeta = i.ObjectMeta 16 | i.Spec.convertTo(&toSourceResolver.Spec) 17 | i.Status.convertTo(&toSourceResolver.Status) 18 | default: 19 | return fmt.Errorf("unknown version, got: %T", toSourceResolver) 20 | } 21 | return nil 22 | } 23 | 24 | func (i *SourceResolver) ConvertFrom(_ context.Context, from apis.Convertible) error { 25 | switch fromSourceResolver := from.(type) { 26 | case *v1alpha1.SourceResolver: 27 | i.ObjectMeta = fromSourceResolver.ObjectMeta 28 | i.Spec.convertFrom(&fromSourceResolver.Spec) 29 | i.Status.convertFrom(&fromSourceResolver.Status) 30 | default: 31 | return fmt.Errorf("unknown version, got: %T", fromSourceResolver) 32 | } 33 | 34 | return nil 35 | } 36 | 37 | func (is *SourceResolverSpec) convertTo(to *v1alpha1.SourceResolverSpec) { 38 | to.Source = is.Source 39 | to.ServiceAccount = is.ServiceAccountName 40 | } 41 | 42 | func (is *SourceResolverSpec) convertFrom(from *v1alpha1.SourceResolverSpec) { 43 | is.Source = from.Source 44 | is.ServiceAccountName = from.ServiceAccount 45 | } 46 | 47 | func (is *SourceResolverStatus) convertFrom(from *v1alpha1.SourceResolverStatus) { 48 | is.Status = from.Status 49 | is.Source = from.Source 50 | } 51 | 52 | func (is *SourceResolverStatus) convertTo(to *v1alpha1.SourceResolverStatus) { 53 | to.Status = is.Status 54 | to.Source = is.Source 55 | } 56 | -------------------------------------------------------------------------------- /pkg/apis/build/v1alpha2/source_resolver_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | 7 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 8 | ) 9 | 10 | const ( 11 | SourceResolverKind = "SourceResolver" 12 | SourceResolverCRName = "sourceresolvers.kpack.io" 13 | ) 14 | 15 | // +genclient 16 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 17 | 18 | // +k8s:openapi-gen=true 19 | type SourceResolver struct { 20 | metav1.TypeMeta `json:",inline"` 21 | metav1.ObjectMeta `json:"metadata,omitempty"` 22 | Spec SourceResolverSpec `json:"spec"` 23 | Status SourceResolverStatus `json:"status,omitempty"` 24 | } 25 | 26 | // +k8s:openapi-gen=true 27 | type SourceResolverSpec struct { 28 | ServiceAccountName string `json:"serviceAccount,omitempty"` 29 | Source corev1alpha1.SourceConfig `json:"source"` 30 | } 31 | 32 | // +k8s:openapi-gen=true 33 | type SourceResolverStatus struct { 34 | corev1alpha1.Status `json:",inline"` 35 | Source corev1alpha1.ResolvedSourceConfig `json:"source,omitempty"` 36 | } 37 | 38 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 39 | 40 | // +k8s:openapi-gen=true 41 | type SourceResolverList struct { 42 | metav1.TypeMeta `json:",inline"` 43 | metav1.ListMeta `json:"metadata"` 44 | 45 | // +k8s:listType=atomic 46 | Items []SourceResolver `json:"items"` 47 | } 48 | 49 | func (*SourceResolver) GetGroupVersionKind() schema.GroupVersionKind { 50 | return SchemeGroupVersion.WithKind("SourceResolver") 51 | } 52 | -------------------------------------------------------------------------------- /pkg/apis/core/v1alpha1/build_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | ) 6 | 7 | // +k8s:openapi-gen=true 8 | // +k8s:deepcopy-gen=true 9 | type BuildStack struct { 10 | RunImage string `json:"runImage,omitempty"` 11 | ID string `json:"id,omitempty"` 12 | } 13 | 14 | // +k8s:openapi-gen=true 15 | // +k8s:deepcopy-gen=true 16 | type BuildBuilderSpec struct { 17 | Image string `json:"image,omitempty"` 18 | // +patchMergeKey=name 19 | // +patchStrategy=merge 20 | // +listType 21 | ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,15,rep,name=imagePullSecrets"` 22 | 23 | // Ref reference to an existing Builder/ClusterBuilder 24 | Ref *corev1.ObjectReference `json:"ref,omitempty"` 25 | } 26 | 27 | // +k8s:openapi-gen=true 28 | // +k8s:deepcopy-gen=true 29 | type CNBBindings []CNBBinding 30 | 31 | // +k8s:openapi-gen=true 32 | // +k8s:deepcopy-gen=true 33 | type CNBBinding struct { 34 | Name string `json:"name,omitempty"` 35 | MetadataRef *corev1.LocalObjectReference `json:"metadataRef,omitempty"` 36 | SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"` 37 | } 38 | -------------------------------------------------------------------------------- /pkg/apis/core/v1alpha1/buildpack_metadata.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | type BuildpackMetadataList []BuildpackMetadata 4 | 5 | // +k8s:openapi-gen=true 6 | // +k8s:deepcopy-gen=true 7 | type BuildpackMetadata struct { 8 | Id string `json:"id"` 9 | Version string `json:"version"` 10 | Homepage string `json:"homepage,omitempty"` 11 | } 12 | 13 | func (l BuildpackMetadataList) Include(q BuildpackMetadata) bool { 14 | for _, bp := range l { 15 | if bp.Id == q.Id && bp.Version == q.Version { 16 | return true 17 | } 18 | } 19 | 20 | return false 21 | } 22 | -------------------------------------------------------------------------------- /pkg/apis/core/v1alpha1/buildpack_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import "fmt" 4 | 5 | // +k8s:openapi-gen=true 6 | // +k8s:deepcopy-gen=true 7 | type ImageSource struct { 8 | Image string `json:"image,omitempty"` 9 | } 10 | 11 | // +k8s:openapi-gen=true 12 | // +k8s:deepcopy-gen=true 13 | type BuildpackStatus struct { 14 | BuildpackInfo `json:",inline"` 15 | Buildpackage BuildpackageInfo `json:"buildpackage,omitempty"` 16 | StoreImage ImageSource `json:"storeImage,omitempty"` 17 | DiffId string `json:"diffId,omitempty"` 18 | Digest string `json:"digest,omitempty"` 19 | Size int64 `json:"size,omitempty"` 20 | API string `json:"api,omitempty"` 21 | Homepage string `json:"homepage,omitempty"` 22 | // +listType 23 | Order []OrderEntry `json:"order,omitempty"` 24 | // +listType 25 | Stacks []BuildpackStack `json:"stacks,omitempty"` 26 | } 27 | 28 | type Order []OrderEntry 29 | 30 | // +k8s:openapi-gen=true 31 | // +k8s:deepcopy-gen=true 32 | type OrderEntry struct { 33 | // +listType 34 | Group []BuildpackRef `json:"group,omitempty"` 35 | } 36 | 37 | // +k8s:openapi-gen=true 38 | // +k8s:deepcopy-gen=true 39 | type BuildpackRef struct { 40 | BuildpackInfo `json:",inline"` 41 | Optional bool `json:"optional,omitempty"` 42 | } 43 | 44 | // +k8s:openapi-gen=true 45 | // +k8s:deepcopy-gen=true 46 | type BuildpackInfo struct { 47 | Id string `json:"id"` 48 | Version string `json:"version,omitempty"` 49 | } 50 | 51 | func (b BuildpackInfo) String() string { 52 | return fmt.Sprintf("%s@%s", b.Id, b.Version) 53 | } 54 | 55 | // +k8s:openapi-gen=true 56 | // +k8s:deepcopy-gen=true 57 | type BuildpackStack struct { 58 | ID string `json:"id"` 59 | 60 | // +listType 61 | Mixins []string `json:"mixins,omitempty"` 62 | } 63 | -------------------------------------------------------------------------------- /pkg/apis/core/v1alpha1/buildpackage_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | // +k8s:openapi-gen=true 4 | // +k8s:deepcopy-gen=true 5 | type BuildpackageInfo struct { 6 | Id string `json:"id,omitempty"` 7 | Version string `json:"version,omitempty"` 8 | Homepage string `json:"homepage,omitempty"` 9 | } 10 | -------------------------------------------------------------------------------- /pkg/apis/core/v1alpha1/image_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | type ImageTaggingStrategy string 4 | 5 | const ( 6 | None ImageTaggingStrategy = "None" 7 | BuildNumber ImageTaggingStrategy = "BuildNumber" 8 | ) 9 | -------------------------------------------------------------------------------- /pkg/apis/core/v1alpha1/notary_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | // +k8s:openapi-gen=true 4 | // +k8s:deepcopy-gen=true 5 | type NotaryConfig struct { 6 | V1 *NotaryV1Config `json:"v1,omitempty"` 7 | } 8 | 9 | // +k8s:openapi-gen=true 10 | // +k8s:deepcopy-gen=true 11 | type NotaryV1Config struct { 12 | URL string `json:"url"` 13 | SecretRef NotarySecretRef `json:"secretRef"` 14 | } 15 | 16 | // +k8s:openapi-gen=true 17 | // +k8s:deepcopy-gen=true 18 | type NotarySecretRef struct { 19 | Name string `json:"name"` 20 | } 21 | -------------------------------------------------------------------------------- /pkg/apis/core/v1alpha1/notary_validation.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "context" 5 | 6 | "knative.dev/pkg/apis" 7 | 8 | "github.com/pivotal/kpack/pkg/apis/validate" 9 | ) 10 | 11 | func (n *NotaryConfig) Validate(ctx context.Context) *apis.FieldError { 12 | if n == nil { 13 | return nil 14 | } 15 | return n.V1.Validate(ctx).ViaField("v1") 16 | } 17 | 18 | func (n *NotaryV1Config) Validate(ctx context.Context) *apis.FieldError { 19 | if n == nil { 20 | return nil 21 | } 22 | return validate.FieldNotEmpty(n.URL, "url"). 23 | Also(validate.FieldNotEmpty(n.SecretRef.Name, "secretRef.name")) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/apis/core/v1alpha1/service_bindings.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | ) 6 | 7 | type ServiceBinding struct { 8 | Name string 9 | SecretRef *corev1.LocalObjectReference 10 | } 11 | 12 | func (s *ServiceBinding) ServiceName() string { 13 | return s.Name 14 | } 15 | 16 | type CNBServiceBinding struct { 17 | Name string 18 | SecretRef *corev1.LocalObjectReference 19 | MetadataRef *corev1.LocalObjectReference 20 | } 21 | 22 | func (v *CNBServiceBinding) ServiceName() string { 23 | return v.Name 24 | } 25 | -------------------------------------------------------------------------------- /pkg/apis/core/v1alpha1/source_validation.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "context" 5 | 6 | "knative.dev/pkg/apis" 7 | 8 | "github.com/pivotal/kpack/pkg/apis/validate" 9 | ) 10 | 11 | func (s *SourceConfig) Validate(ctx context.Context) *apis.FieldError { 12 | sources := make([]string, 0, 3) 13 | if s.Git != nil { 14 | sources = append(sources, "git") 15 | } 16 | if s.Blob != nil { 17 | sources = append(sources, "blob") 18 | } 19 | if s.Registry != nil { 20 | sources = append(sources, "registry") 21 | } 22 | 23 | if len(sources) == 0 { 24 | return apis.ErrMissingOneOf("git", "blob", "registry") 25 | } 26 | 27 | if len(sources) != 1 { 28 | return apis.ErrMultipleOneOf(sources...) 29 | } 30 | 31 | return (s.Git.Validate(ctx).ViaField("git")). 32 | Also(s.Blob.Validate(ctx).ViaField("blob")). 33 | Also(s.Registry.Validate(ctx).ViaField("registry")) 34 | } 35 | 36 | func (g *Git) Validate(ctx context.Context) *apis.FieldError { 37 | if g == nil { 38 | return nil 39 | } 40 | 41 | return validate.FieldNotEmpty(g.URL, "url"). 42 | Also(validate.FieldNotEmpty(g.Revision, "revision")) 43 | } 44 | 45 | func (b *Blob) Validate(ctx context.Context) *apis.FieldError { 46 | if b == nil { 47 | return nil 48 | } 49 | 50 | if b.Auth != "" && b.Auth != "helper" && b.Auth != "secret" { 51 | return apis.ErrInvalidValue(b.Auth, "auth", "must be one of '', 'helper', or 'secret'") 52 | } 53 | 54 | fieldError := validate.FieldNotEmpty(b.URL, "url"). 55 | Also(validate.StripComponents(b.StripComponents)) 56 | 57 | return fieldError 58 | } 59 | 60 | func (r *Registry) Validate(ctx context.Context) *apis.FieldError { 61 | if r == nil { 62 | return nil 63 | } 64 | 65 | return validate.Image(r.Image) 66 | } 67 | -------------------------------------------------------------------------------- /pkg/apis/core/v1alpha1/status.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package v1alpha1 16 | 17 | import ( 18 | corev1 "k8s.io/api/core/v1" 19 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 | ) 21 | 22 | func CreateStatusWithReadyCondition(generation int64, err error) Status { 23 | msg := "" 24 | conditionStatus := corev1.ConditionTrue 25 | 26 | if err != nil { 27 | msg = err.Error() 28 | conditionStatus = corev1.ConditionFalse 29 | } 30 | 31 | return Status{ 32 | ObservedGeneration: generation, 33 | Conditions: Conditions{ 34 | { 35 | Type: ConditionReady, 36 | Status: conditionStatus, 37 | LastTransitionTime: VolatileTime{Inner: metav1.Now()}, 38 | Message: msg, 39 | }, 40 | }, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/apis/core/v1alpha1/volatile_time.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Knative Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/api/equality" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // VolatileTime wraps metav1.Time 25 | // +k8s:deepcopy-gen=true 26 | // +k8s:openapi-gen=true 27 | type VolatileTime struct { 28 | Inner metav1.Time `json:"inner"` 29 | } 30 | 31 | // MarshalJSON implements the json.Marshaler interface. 32 | func (t VolatileTime) MarshalJSON() ([]byte, error) { 33 | return t.Inner.MarshalJSON() 34 | } 35 | 36 | // UnmarshalJSON implements the json.Unmarshaller interface. 37 | func (t *VolatileTime) UnmarshalJSON(b []byte) error { 38 | return t.Inner.UnmarshalJSON(b) 39 | } 40 | 41 | func init() { 42 | equality.Semantic.AddFunc( 43 | // Always treat VolatileTime fields as equivalent. 44 | func(a, b VolatileTime) bool { 45 | return true 46 | }, 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /pkg/apis/validate/validation_helpers.go: -------------------------------------------------------------------------------- 1 | package validate 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/google/go-containerregistry/pkg/name" 7 | "knative.dev/pkg/apis" 8 | ) 9 | 10 | func FieldNotEmpty(value, field string) *apis.FieldError { 11 | if value == "" { 12 | return apis.ErrMissingField(field) 13 | } 14 | return nil 15 | } 16 | 17 | func ListNotEmpty(value []string, field string) *apis.FieldError { 18 | if len(value) == 0 { 19 | return apis.ErrMissingField(field) 20 | } 21 | return nil 22 | } 23 | 24 | func ImmutableField(original, current interface{}, field string) *apis.FieldError { 25 | if original != current { 26 | return &apis.FieldError{ 27 | Message: "Immutable field changed", 28 | Paths: []string{field}, 29 | Details: fmt.Sprintf("got: %v, want: %v", current, original), 30 | } 31 | } 32 | return nil 33 | } 34 | 35 | func Tag(value string) *apis.FieldError { 36 | if value == "" { 37 | return apis.ErrMissingField("tag") 38 | } 39 | 40 | _, err := name.NewTag(value, name.WeakValidation) 41 | if err != nil { 42 | return apis.ErrInvalidValue(value, "tag") 43 | } 44 | return nil 45 | } 46 | 47 | func Tags(tags []string, fieldName string) *apis.FieldError { 48 | var errors *apis.FieldError = nil 49 | for i, tag := range tags { 50 | _, err := name.NewTag(tag, name.WeakValidation) 51 | if err != nil { 52 | //noinspection GoNilness 53 | errors = errors.Also(apis.ErrInvalidArrayValue(tag, fieldName, i)) 54 | } 55 | } 56 | return errors 57 | } 58 | 59 | func Image(value string) *apis.FieldError { 60 | if value == "" { 61 | return apis.ErrMissingField("image") 62 | } 63 | 64 | _, err := name.ParseReference(value, name.WeakValidation) 65 | if err != nil { 66 | return apis.ErrInvalidValue(value, "image") 67 | } 68 | return nil 69 | } 70 | 71 | func StripComponents(value int64) *apis.FieldError { 72 | if value >= 0 { 73 | return nil 74 | } 75 | 76 | return apis.ErrGeneric(fmt.Sprintf("expected positive integer got %d", value), "stripComponents") 77 | } 78 | -------------------------------------------------------------------------------- /pkg/arch_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/matthewmcnew/archtest" 7 | ) 8 | 9 | func TestDependencies(t *testing.T) { 10 | archtest.Package(t, "github.com/pivotal/kpack/..."). 11 | IncludeTests(). 12 | Ignoring("github.com/pivotal/kpack/hack"). 13 | ShouldNotDependDirectlyOn( 14 | "gotest.tools/v3/assert", 15 | "gotest.tools/v3/assert/cmp", 16 | "gotest.tools/assert", 17 | "gotest.tools/assert/cmp", 18 | 19 | "github.com/tj/assert", 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/blob/azure_keychain.go: -------------------------------------------------------------------------------- 1 | package blob 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "regexp" 8 | "time" 9 | 10 | "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" 11 | "github.com/Azure/azure-sdk-for-go/sdk/azidentity" 12 | blobsas "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" 13 | filesas "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/sas" 14 | ) 15 | 16 | var ( 17 | azScope = "https://storage.azure.com/.default" 18 | azRegex = regexp.MustCompile(`.*[a-z0-9]+\.([a-z]+)\.core\.windows\.net\/.*`) 19 | ) 20 | 21 | type azKeychain struct{} 22 | 23 | func (a azKeychain) Resolve(url string) (string, map[string]string, error) { 24 | submatches := azRegex.FindStringSubmatch(url) 25 | if len(submatches) != 2 { 26 | return "", nil, fmt.Errorf("not an azure url") 27 | } 28 | service := submatches[1] 29 | 30 | cred, err := azidentity.NewDefaultAzureCredential(nil) 31 | if err != nil { 32 | return "", nil, err 33 | } 34 | 35 | tk, err := cred.GetToken(context.Background(), policy.TokenRequestOptions{Scopes: []string{azScope}}) 36 | if err != nil { 37 | return "", nil, err 38 | } 39 | 40 | headers := map[string]string{ 41 | "x-ms-date": time.Now().Format(http.TimeFormat), 42 | } 43 | 44 | switch service { 45 | case "file": 46 | headers["x-ms-version"] = filesas.Version 47 | // https://learn.microsoft.com/en-us/rest/api/storageservices/get-file 48 | headers["x-ms-file-request-intent"] = "backup" 49 | case "blob": 50 | headers["x-ms-version"] = blobsas.Version 51 | default: 52 | return "", nil, fmt.Errorf("only azure blob and filestore are supported") 53 | } 54 | 55 | return "Bearer " + tk.Token, headers, nil 56 | } 57 | -------------------------------------------------------------------------------- /pkg/blob/gcp_keychain.go: -------------------------------------------------------------------------------- 1 | package blob 2 | 3 | import ( 4 | "context" 5 | 6 | "golang.org/x/oauth2/google" 7 | ) 8 | 9 | const ( 10 | gcpScope = "https://www.googleapis.com/auth/devstorage.read_only" 11 | ) 12 | 13 | type gcpKeychain struct{} 14 | 15 | func (g gcpKeychain) Resolve(url string) (string, map[string]string, error) { 16 | ctx := context.Background() 17 | creds, err := google.FindDefaultCredentials(ctx, gcpScope) 18 | if err != nil { 19 | return "", nil, err 20 | } 21 | 22 | tk, err := creds.TokenSource.Token() 23 | if err != nil { 24 | return "", nil, err 25 | } 26 | 27 | return "Bearer " + tk.AccessToken, nil, nil 28 | } 29 | -------------------------------------------------------------------------------- /pkg/blob/keychain.go: -------------------------------------------------------------------------------- 1 | package blob 2 | 3 | import "fmt" 4 | 5 | type Keychain interface { 6 | Resolve(url string) (authHeader string, headers map[string]string, err error) 7 | } 8 | 9 | var DefaultKeychain = NewMultiKeychain( 10 | azKeychain{}, 11 | gcpKeychain{}, 12 | ) 13 | 14 | type multiKeychain struct { 15 | keychains []Keychain 16 | } 17 | 18 | func NewMultiKeychain(creds ...Keychain) Keychain { 19 | return &multiKeychain{creds} 20 | } 21 | 22 | func (m *multiKeychain) Resolve(url string) (string, map[string]string, error) { 23 | for _, helper := range m.keychains { 24 | t, h, err := helper.Resolve(url) 25 | if t != "" { 26 | return t, h, err 27 | } 28 | } 29 | return "", nil, fmt.Errorf("no keychain matched for '%v'", url) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/blob/keychain_test.go: -------------------------------------------------------------------------------- 1 | package blob_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/pivotal/kpack/pkg/blob" 8 | "github.com/sclevine/spec" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestKeychain(t *testing.T) { 13 | spec.Run(t, "testKeychain", testKeychain) 14 | } 15 | 16 | func testKeychain(t *testing.T, when spec.G, it spec.S) { 17 | var ( 18 | goodKeychain1 = &fakeKeychain{"some-auth", nil, nil} 19 | goodKeychain2 = &fakeKeychain{"some-other-auth", map[string]string{"some-header": "some-value"}, nil} 20 | badKeychain1 = &fakeKeychain{"", nil, fmt.Errorf("some-error")} 21 | badKeychain2 = &fakeKeychain{"", nil, fmt.Errorf("some-other-error")} 22 | ) 23 | when("multi keychain", func() { 24 | it("resolves them in order", func() { 25 | keychain := blob.NewMultiKeychain( 26 | goodKeychain1, 27 | goodKeychain2, 28 | ) 29 | 30 | auth, header, err := keychain.Resolve("https://some-url.com") 31 | require.NoError(t, err) 32 | 33 | require.Equal(t, "some-auth", auth) 34 | require.Nil(t, header) 35 | }) 36 | 37 | it("returns the first one non-empty result", func() { 38 | keychain := blob.NewMultiKeychain( 39 | badKeychain1, 40 | badKeychain2, 41 | goodKeychain2, 42 | ) 43 | 44 | auth, header, err := keychain.Resolve("https://some-url.com") 45 | require.NoError(t, err) 46 | 47 | require.Equal(t, "some-other-auth", auth) 48 | require.Contains(t, header, "some-header") 49 | }) 50 | 51 | it("errors if no keychain matches", func() { 52 | keychain := blob.NewMultiKeychain( 53 | badKeychain1, 54 | badKeychain2, 55 | ) 56 | 57 | _, _, err := keychain.Resolve("https://some-url.com") 58 | require.EqualError(t, err, "no keychain matched for 'https://some-url.com'") 59 | }) 60 | }) 61 | } 62 | 63 | type fakeKeychain struct { 64 | auth string 65 | header map[string]string 66 | err error 67 | } 68 | 69 | func (f fakeKeychain) Resolve(_ string) (string, map[string]string, error) { 70 | return f.auth, f.header, f.err 71 | } 72 | -------------------------------------------------------------------------------- /pkg/blob/resolver.go: -------------------------------------------------------------------------------- 1 | package blob 2 | 3 | import ( 4 | "context" 5 | 6 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 7 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 8 | ) 9 | 10 | type Resolver struct { 11 | } 12 | 13 | func (*Resolver) Resolve(ctx context.Context, sourceResolver *buildapi.SourceResolver) (corev1alpha1.ResolvedSourceConfig, error) { 14 | return corev1alpha1.ResolvedSourceConfig{ 15 | Blob: &corev1alpha1.ResolvedBlobSource{ 16 | URL: sourceResolver.Spec.Source.Blob.URL, 17 | Auth: sourceResolver.Spec.Source.Blob.Auth, 18 | SubPath: sourceResolver.Spec.Source.SubPath, 19 | }, 20 | }, nil 21 | } 22 | 23 | func (*Resolver) CanResolve(sourceResolver *buildapi.SourceResolver) bool { 24 | return sourceResolver.IsBlob() 25 | } 26 | -------------------------------------------------------------------------------- /pkg/blob/testdata/fat-zip.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/pkg/blob/testdata/fat-zip.zip -------------------------------------------------------------------------------- /pkg/blob/testdata/parent.tar: -------------------------------------------------------------------------------- 1 | parent/000755 000765 000024 00000000000 14240243740 015227 5ustar00diarmuid.macnamarastaff000000 000000 parent/child.txt000644 000765 000024 00000000000 14240243740 017041 0ustar00diarmuid.macnamarastaff000000 000000 -------------------------------------------------------------------------------- /pkg/blob/testdata/parent.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/pkg/blob/testdata/parent.tar.gz -------------------------------------------------------------------------------- /pkg/blob/testdata/parent.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/pkg/blob/testdata/parent.zip -------------------------------------------------------------------------------- /pkg/blob/testdata/test.html: -------------------------------------------------------------------------------- 1 | invalid blob 2 | -------------------------------------------------------------------------------- /pkg/blob/testdata/test.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/pkg/blob/testdata/test.tar.gz -------------------------------------------------------------------------------- /pkg/blob/testdata/test.txt: -------------------------------------------------------------------------------- 1 | bad blob 2 | -------------------------------------------------------------------------------- /pkg/blob/testdata/test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/pkg/blob/testdata/test.zip -------------------------------------------------------------------------------- /pkg/buildchange/buildpack_change.go: -------------------------------------------------------------------------------- 1 | package buildchange 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | 8 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 9 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 10 | ) 11 | 12 | func NewBuildpackChange(oldBuildpacks, newBuildpacks []corev1alpha1.BuildpackInfo) Change { 13 | return buildpackChange{ 14 | old: oldBuildpacks, 15 | new: newBuildpacks, 16 | } 17 | } 18 | 19 | type buildpackChange struct { 20 | old []corev1alpha1.BuildpackInfo 21 | new []corev1alpha1.BuildpackInfo 22 | } 23 | 24 | func (b buildpackChange) Reason() buildapi.BuildReason { return buildapi.BuildReasonBuildpack } 25 | 26 | func (b buildpackChange) IsBuildRequired() (bool, error) { 27 | sort.Slice(b.old, func(i, j int) bool { 28 | return b.old[i].Id < b.old[j].Id 29 | }) 30 | sort.Slice(b.new, func(i, j int) bool { 31 | return b.new[i].Id < b.new[j].Id 32 | }) 33 | return !cmp.Equal(b.old, b.new), nil 34 | } 35 | 36 | func (b buildpackChange) Old() interface{} { return b.old } 37 | 38 | func (b buildpackChange) New() interface{} { return b.new } 39 | 40 | func (b buildpackChange) Priority() buildapi.BuildPriority { return buildapi.BuildPriorityLow } 41 | -------------------------------------------------------------------------------- /pkg/buildchange/change.go: -------------------------------------------------------------------------------- 1 | package buildchange 2 | 3 | import ( 4 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 5 | ) 6 | 7 | type Change interface { 8 | Reason() buildapi.BuildReason 9 | IsBuildRequired() (bool, error) 10 | Old() interface{} 11 | New() interface{} 12 | Priority() buildapi.BuildPriority 13 | } 14 | 15 | type GenericChange struct { 16 | Reason string `json:"reason,omitempty"` 17 | Old interface{} `json:"old,omitempty"` 18 | New interface{} `json:"new,omitempty"` 19 | Priority buildapi.BuildPriority `json:"-"` 20 | } 21 | 22 | func newGenericChange(change Change) GenericChange { 23 | return GenericChange{ 24 | Reason: string(change.Reason()), 25 | Old: change.Old(), 26 | New: change.New(), 27 | Priority: change.Priority(), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pkg/buildchange/change_logger.go: -------------------------------------------------------------------------------- 1 | package buildchange 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "strings" 8 | 9 | "github.com/pkg/errors" 10 | 11 | "github.com/pivotal/kpack/pkg/differ" 12 | ) 13 | 14 | const differPrefix = "\t" 15 | 16 | func Log(logger *log.Logger, changesStr string) error { 17 | return NewChangeLogger(logger, changesStr).Log() 18 | } 19 | 20 | func NewChangeLogger(logger *log.Logger, changesStr string) *changeLogger { 21 | options := differ.DefaultOptions() 22 | options.Prefix = differPrefix 23 | 24 | return &changeLogger{ 25 | logger: logger, 26 | changesStr: changesStr, 27 | differ: differ.NewDiffer(options), 28 | } 29 | } 30 | 31 | type changeLogger struct { 32 | logger *log.Logger 33 | changesStr string 34 | 35 | differ differ.Differ 36 | reasons []string 37 | changes []GenericChange 38 | } 39 | 40 | func (c *changeLogger) Log() error { 41 | if c.changesStr == "" { 42 | return nil 43 | } 44 | 45 | if err := c.parseChanges(); err != nil { 46 | return errors.Wrapf(err, "error parsing build changes JSON string '%s'", c.changesStr) 47 | } 48 | c.parseReasons() 49 | 50 | c.logReasons() 51 | return c.logChanges() 52 | } 53 | 54 | func (c *changeLogger) parseChanges() error { 55 | c.changes = []GenericChange{} 56 | return json.Unmarshal([]byte(c.changesStr), &c.changes) 57 | } 58 | 59 | func (c *changeLogger) parseReasons() { 60 | c.reasons = make([]string, len(c.changes)) 61 | for i, change := range c.changes { 62 | c.reasons[i] = change.Reason 63 | } 64 | } 65 | 66 | func (c *changeLogger) logReasons() { 67 | reasons := strings.Join(c.reasons, reasonsSeparator) 68 | c.logger.Printf("Build reason(s): %s\n", reasons) 69 | } 70 | 71 | func (c *changeLogger) logChanges() error { 72 | for _, change := range c.changes { 73 | diff, err := c.differ.Diff(change.Old, change.New) 74 | if err != nil { 75 | return errors.Wrapf(err, "error logging change for reason '%s'", change.Reason) 76 | } 77 | 78 | changeHeader := fmt.Sprintf("%s:\n", change.Reason) 79 | c.logger.Print(changeHeader) 80 | c.logger.Print(diff) 81 | } 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /pkg/buildchange/change_summary.go: -------------------------------------------------------------------------------- 1 | package buildchange 2 | 3 | import ( 4 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 5 | "github.com/pkg/errors" 6 | ) 7 | 8 | type ChangeSummary struct { 9 | HasChanges bool 10 | ReasonsStr string 11 | ChangesStr string 12 | Priority buildapi.BuildPriority 13 | } 14 | 15 | func NewChangeSummary(hasChanges bool, reasonsStr, changesStr string, priority buildapi.BuildPriority) (ChangeSummary, error) { 16 | cs := ChangeSummary{ 17 | HasChanges: hasChanges, 18 | ReasonsStr: reasonsStr, 19 | ChangesStr: changesStr, 20 | Priority: priority, 21 | } 22 | 23 | if !cs.IsValid() { 24 | return cs, errors.Errorf("invalid change summary '%+v'", cs) 25 | } 26 | return cs, nil 27 | } 28 | 29 | func (c ChangeSummary) IsValid() bool { 30 | if c.HasChanges { 31 | return c.ReasonsStr != "" && c.ChangesStr != "" 32 | } else { 33 | return c.ReasonsStr == "" && c.ChangesStr == "" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pkg/buildchange/commit_change.go: -------------------------------------------------------------------------------- 1 | package buildchange 2 | 3 | import buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 4 | 5 | func NewCommitChange(oldRevision, newRevision string) Change { 6 | return commitChange{ 7 | oldRevision: oldRevision, 8 | newRevision: newRevision, 9 | } 10 | } 11 | 12 | type commitChange struct { 13 | newRevision string 14 | oldRevision string 15 | } 16 | 17 | func (c commitChange) Reason() buildapi.BuildReason { return buildapi.BuildReasonCommit } 18 | 19 | func (c commitChange) IsBuildRequired() (bool, error) { return c.oldRevision != c.newRevision, nil } 20 | 21 | func (c commitChange) Old() interface{} { return c.oldRevision } 22 | 23 | func (c commitChange) New() interface{} { return c.newRevision } 24 | 25 | func (c commitChange) Priority() buildapi.BuildPriority { return buildapi.BuildPriorityHigh } 26 | -------------------------------------------------------------------------------- /pkg/buildchange/config_change.go: -------------------------------------------------------------------------------- 1 | package buildchange 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | "k8s.io/apimachinery/pkg/api/equality" 6 | 7 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 8 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 9 | ) 10 | 11 | func NewConfigChange(oldConfig, newConfig Config) Change { 12 | return configChange{ 13 | old: oldConfig, 14 | new: newConfig, 15 | } 16 | } 17 | 18 | type configChange struct { 19 | old Config 20 | new Config 21 | } 22 | 23 | type Config struct { 24 | Env []corev1.EnvVar `json:"env,omitempty"` 25 | Resources corev1.ResourceRequirements `json:"resources,omitempty"` 26 | Services buildapi.Services `json:"services,omitempty"` 27 | CNBBindings corev1alpha1.CNBBindings `json:"cnbBindings,omitempty"` 28 | Source corev1alpha1.SourceConfig `json:"source,omitempty"` 29 | } 30 | 31 | func (c configChange) Reason() buildapi.BuildReason { return buildapi.BuildReasonConfig } 32 | 33 | func (c configChange) IsBuildRequired() (bool, error) { 34 | // Git revision changes are considered as COMMIT change 35 | // Ignore them as part of CONFIG Change 36 | var oldGitRevision, newGitRevision string 37 | 38 | if c.old.Source.Git != nil { 39 | oldGitRevision = c.old.Source.Git.Revision 40 | c.old.Source.Git.Revision = "" 41 | } 42 | if c.new.Source.Git != nil { 43 | newGitRevision = c.new.Source.Git.Revision 44 | c.new.Source.Git.Revision = "" 45 | } 46 | 47 | valid := !equality.Semantic.DeepEqual(c.old, c.new) 48 | 49 | if c.old.Source.Git != nil { 50 | c.old.Source.Git.Revision = oldGitRevision 51 | } 52 | if c.new.Source.Git != nil { 53 | c.new.Source.Git.Revision = newGitRevision 54 | } 55 | return valid, nil 56 | } 57 | 58 | func (c configChange) Old() interface{} { return c.old } 59 | 60 | func (c configChange) New() interface{} { return c.new } 61 | 62 | func (c configChange) Priority() buildapi.BuildPriority { return buildapi.BuildPriorityHigh } 63 | -------------------------------------------------------------------------------- /pkg/buildchange/lifecycle_change.go: -------------------------------------------------------------------------------- 1 | package buildchange 2 | 3 | import ( 4 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 5 | ) 6 | 7 | func NewLifecycleChange(oldLifecycle, newLifecycle string) Change { 8 | return lifecycleChange{ 9 | oldLifecycle: oldLifecycle, 10 | newLifecycle: newLifecycle, 11 | } 12 | } 13 | 14 | type lifecycleChange struct { 15 | oldLifecycle string 16 | newLifecycle string 17 | err error 18 | } 19 | 20 | func (l lifecycleChange) Reason() buildapi.BuildReason { return buildapi.BuildReasonLifecycle } 21 | 22 | func (l lifecycleChange) IsBuildRequired() (bool, error) { 23 | return l.oldLifecycle != l.newLifecycle, l.err 24 | } 25 | 26 | func (l lifecycleChange) Old() interface{} { return l.oldLifecycle } 27 | 28 | func (l lifecycleChange) New() interface{} { return l.newLifecycle } 29 | 30 | func (l lifecycleChange) Priority() buildapi.BuildPriority { return buildapi.BuildPriorityLow } 31 | -------------------------------------------------------------------------------- /pkg/buildchange/progress_logger_test.go: -------------------------------------------------------------------------------- 1 | package buildchange_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | k8sfake "k8s.io/client-go/kubernetes/fake" 9 | 10 | "github.com/pivotal/kpack/pkg/buildchange" 11 | "github.com/sclevine/spec" 12 | corev1 "k8s.io/api/core/v1" 13 | ) 14 | 15 | func TestProgressLogger(t *testing.T) { 16 | spec.Run(t, "ProgressLogger", testProgressLogger) 17 | } 18 | 19 | func testProgressLogger(t *testing.T, when spec.G, it spec.S) { 20 | // Create a fake Kubernetes client 21 | k8sClient := k8sfake.NewSimpleClientset() 22 | pl := &buildchange.ProgressLogger{ 23 | K8sClient: k8sClient, 24 | } 25 | 26 | when("Pod terminated unsuccessfully", func() { 27 | it("returns the status for the container that terminated with an error", func() { 28 | pod := &corev1.Pod{ 29 | ObjectMeta: metav1.ObjectMeta{ 30 | Name: "some-name", 31 | Namespace: "test", 32 | }, 33 | } 34 | containerStatus := &corev1.ContainerStatus{ 35 | Name: "prepare", 36 | State: corev1.ContainerState{ 37 | Terminated: &corev1.ContainerStateTerminated{ 38 | Message: "Container detect terminated with error", 39 | }, 40 | }, 41 | } 42 | 43 | podTeminationMessage, err := pl.GetTerminationMessage(pod, containerStatus) 44 | assert.NoError(t, err) 45 | assert.Equal(t, " Container detect terminated with error fake logs: For more info use `kubectl logs -n test some-name -c prepare`", podTeminationMessage) 46 | }) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /pkg/buildchange/stack_change.go: -------------------------------------------------------------------------------- 1 | package buildchange 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/google/go-containerregistry/pkg/name" 7 | "github.com/pkg/errors" 8 | 9 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 10 | ) 11 | 12 | func NewStackChange(oldRunImageRefStr, newRunImageRefStr string) Change { 13 | var change stackChange 14 | var errStrs []string 15 | 16 | oldRunImageRef, err := name.ParseReference(oldRunImageRefStr) 17 | if err != nil { 18 | errStrs = append(errStrs, err.Error()) 19 | } else { 20 | change.oldRunImageDigest = oldRunImageRef.Identifier() 21 | } 22 | 23 | newRunImageRef, err := name.ParseReference(newRunImageRefStr) 24 | if err != nil { 25 | errStrs = append(errStrs, err.Error()) 26 | } else { 27 | change.newRunImageDigest = newRunImageRef.Identifier() 28 | } 29 | 30 | if len(errStrs) > 0 { 31 | change.err = errors.New(strings.Join(errStrs, "; ")) 32 | } 33 | return change 34 | } 35 | 36 | type stackChange struct { 37 | oldRunImageDigest string 38 | newRunImageDigest string 39 | err error 40 | } 41 | 42 | func (s stackChange) Reason() buildapi.BuildReason { return buildapi.BuildReasonStack } 43 | 44 | func (s stackChange) IsBuildRequired() (bool, error) { 45 | return s.oldRunImageDigest != s.newRunImageDigest, s.err 46 | } 47 | 48 | func (s stackChange) Old() interface{} { return s.oldRunImageDigest } 49 | 50 | func (s stackChange) New() interface{} { return s.newRunImageDigest } 51 | 52 | func (s stackChange) Priority() buildapi.BuildPriority { return buildapi.BuildPriorityLow } 53 | -------------------------------------------------------------------------------- /pkg/buildchange/trigger_change.go: -------------------------------------------------------------------------------- 1 | package buildchange 2 | 3 | import ( 4 | "fmt" 5 | 6 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 7 | ) 8 | 9 | func NewTriggerChange(dateStr string) Change { 10 | format := "A new build was manually triggered on %s" 11 | message := fmt.Sprintf(format, dateStr) 12 | 13 | return triggerChange{ 14 | message: message, 15 | } 16 | } 17 | 18 | type triggerChange struct { 19 | message string 20 | } 21 | 22 | func (t triggerChange) Reason() buildapi.BuildReason { return buildapi.BuildReasonTrigger } 23 | 24 | func (t triggerChange) IsBuildRequired() (bool, error) { 25 | return t.message != "", nil 26 | } 27 | 28 | func (t triggerChange) Old() interface{} { return "" } 29 | 30 | func (t triggerChange) New() interface{} { return t.message } 31 | 32 | func (t triggerChange) Priority() buildapi.BuildPriority { return buildapi.BuildPriorityHigh } 33 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated fake clientset. 20 | package fake 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package contains the scheme of the automatically generated clientset. 20 | package scheme 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/build/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated typed clients. 20 | package v1alpha1 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/build/v1alpha1/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // Package fake has the automatically generated clients. 20 | package fake 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/build/v1alpha1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package v1alpha1 20 | 21 | type BuildExpansion interface{} 22 | 23 | type BuilderExpansion interface{} 24 | 25 | type ClusterBuilderExpansion interface{} 26 | 27 | type ClusterStackExpansion interface{} 28 | 29 | type ClusterStoreExpansion interface{} 30 | 31 | type ImageExpansion interface{} 32 | 33 | type SourceResolverExpansion interface{} 34 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/build/v1alpha2/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated typed clients. 20 | package v1alpha2 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/build/v1alpha2/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // Package fake has the automatically generated clients. 20 | package fake 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/build/v1alpha2/generated_expansion.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package v1alpha2 20 | 21 | type BuildExpansion interface{} 22 | 23 | type BuilderExpansion interface{} 24 | 25 | type BuildpackExpansion interface{} 26 | 27 | type ClusterBuilderExpansion interface{} 28 | 29 | type ClusterBuildpackExpansion interface{} 30 | 31 | type ClusterLifecycleExpansion interface{} 32 | 33 | type ClusterStackExpansion interface{} 34 | 35 | type ClusterStoreExpansion interface{} 36 | 37 | type ImageExpansion interface{} 38 | 39 | type SourceResolverExpansion interface{} 40 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package internalinterfaces 20 | 21 | import ( 22 | time "time" 23 | 24 | versioned "github.com/pivotal/kpack/pkg/client/clientset/versioned" 25 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | runtime "k8s.io/apimachinery/pkg/runtime" 27 | cache "k8s.io/client-go/tools/cache" 28 | ) 29 | 30 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. 31 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer 32 | 33 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle 34 | type SharedInformerFactory interface { 35 | Start(stopCh <-chan struct{}) 36 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer 37 | } 38 | 39 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions. 40 | type TweakListOptionsFunc func(*v1.ListOptions) 41 | -------------------------------------------------------------------------------- /pkg/cnb/builder_builder_test.go: -------------------------------------------------------------------------------- 1 | package cnb 2 | 3 | import ( 4 | "testing" 5 | 6 | v1 "github.com/google/go-containerregistry/pkg/v1" 7 | "github.com/google/go-containerregistry/pkg/v1/mutate" 8 | "github.com/google/go-containerregistry/pkg/v1/random" 9 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 10 | "github.com/sclevine/spec" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestBuilderBlder(t *testing.T) { 15 | spec.Run(t, "TestBuilderBlder", testBuilderBlder) 16 | } 17 | 18 | func testBuilderBlder(t *testing.T, when spec.G, it spec.S) { 19 | var ( 20 | builderBuilder *builderBlder 21 | kpackVersion = "0.16.0" 22 | ) 23 | 24 | when("Adding a stack", func() { 25 | it.Before(func() { 26 | builderBuilder = newBuilderBldr(kpackVersion) 27 | }) 28 | 29 | it("errors when the stack is windows", func() { 30 | baseImage := baseImage(t, "windows") 31 | err := builderBuilder.AddStack(baseImage, &buildapi.ClusterStack{}) 32 | assert.EqualError(t, err, "windows base images are not supported") 33 | }) 34 | 35 | it("copies the resolved clusterstack to the builder", func() { 36 | resolvedStack := buildapi.ResolvedClusterStack{ 37 | Id: "some-id", 38 | Mixins: []string{"some-mixin"}, 39 | UserID: 1000, 40 | GroupID: 1000, 41 | } 42 | 43 | baseImage := baseImage(t, "linux") 44 | err := builderBuilder.AddStack(baseImage, &buildapi.ClusterStack{Status: buildapi.ClusterStackStatus{ResolvedClusterStack: resolvedStack}}) 45 | assert.NoError(t, err) 46 | 47 | assert.Equal(t, "some-id", builderBuilder.stackId) 48 | assert.Equal(t, 1000, builderBuilder.cnbUserId) 49 | assert.Equal(t, 1000, builderBuilder.cnbGroupId) 50 | assert.Equal(t, []string{"some-mixin"}, builderBuilder.mixins) 51 | 52 | }) 53 | }) 54 | } 55 | 56 | func baseImage(t *testing.T, os string) v1.Image { 57 | img, err := random.Image(1, 1) 58 | assert.NoError(t, err) 59 | 60 | config, err := img.ConfigFile() 61 | assert.NoError(t, err) 62 | 63 | config.OS = os 64 | 65 | img, err = mutate.ConfigFile(img, config) 66 | assert.NoError(t, err) 67 | 68 | return img 69 | } 70 | -------------------------------------------------------------------------------- /pkg/cnb/builder_layers.go: -------------------------------------------------------------------------------- 1 | package cnb 2 | 3 | type BuildpackLayerMetadata map[string]map[string]BuildpackLayerInfo 4 | 5 | func (l BuildpackLayerMetadata) add(layer buildpackLayer) { 6 | _, ok := l[layer.BuildpackInfo.Id] 7 | if !ok { 8 | l[layer.BuildpackInfo.Id] = map[string]BuildpackLayerInfo{} 9 | } 10 | 11 | l[layer.BuildpackInfo.Id][layer.BuildpackInfo.Version] = layer.BuildpackLayerInfo 12 | } 13 | -------------------------------------------------------------------------------- /pkg/cnb/buildpackage_metadata.go: -------------------------------------------------------------------------------- 1 | package cnb 2 | 3 | const ( 4 | buildpackageMetadataLabel = "io.buildpacks.buildpackage.metadata" 5 | ) 6 | 7 | type BuildpackageMetadata struct { 8 | Id string `json:"id"` 9 | Version string `json:"version"` 10 | Homepage string `json:"homepage"` 11 | } 12 | -------------------------------------------------------------------------------- /pkg/cnb/dependency_tree.go: -------------------------------------------------------------------------------- 1 | package cnb 2 | 3 | import ( 4 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 5 | ) 6 | 7 | type Node struct { 8 | Buildpack *corev1alpha1.BuildpackStatus 9 | Children []*Node 10 | } 11 | 12 | // NewTree generates a list of dependency trees for the given buildpacks. A 13 | // buildpack's dependency tree is just the buildpack's group[].order[] but in 14 | // proper tree form. 15 | func NewTree(buildpacks []corev1alpha1.BuildpackStatus) []*Node { 16 | lookup := make(map[string]*corev1alpha1.BuildpackStatus) 17 | for i := range buildpacks { 18 | // explictly create a new var here, since using pointers in for-loops gets nasty 19 | bp := buildpacks[i] 20 | lookup[bp.Id] = &bp 21 | } 22 | 23 | usedBuildpacks := make(map[string]bool) 24 | for _, bp := range buildpacks { 25 | for _, order := range bp.Order { 26 | for _, status := range order.Group { 27 | usedBuildpacks[status.Id] = true 28 | } 29 | } 30 | } 31 | 32 | var unusedBuildpacks []string 33 | for _, bp := range buildpacks { 34 | if _, found := usedBuildpacks[bp.Id]; !found { 35 | unusedBuildpacks = append(unusedBuildpacks, bp.Id) 36 | } 37 | } 38 | 39 | trees := make([]*Node, len(unusedBuildpacks)) 40 | for i, id := range unusedBuildpacks { 41 | trees[i] = makeTree(lookup, id) 42 | } 43 | 44 | return trees 45 | } 46 | 47 | func makeTree(lookup map[string]*corev1alpha1.BuildpackStatus, id string) *Node { 48 | bp := lookup[id] 49 | 50 | var children []*Node 51 | for _, order := range bp.Order { 52 | for _, status := range order.Group { 53 | children = append(children, makeTree(lookup, status.Id)) 54 | } 55 | } 56 | 57 | return &Node{ 58 | Buildpack: bp, 59 | Children: children, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pkg/cnb/env_vars.go: -------------------------------------------------------------------------------- 1 | package cnb 2 | 3 | import ( 4 | "os" 5 | "path" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func serializeEnvVars(envVars []envVariable, platformDir string) error { 11 | var err error 12 | folder := path.Join(platformDir, "env") 13 | err = os.MkdirAll(folder, os.ModePerm) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | for _, envVar := range envVars { 19 | err = os.WriteFile(path.Join(folder, envVar.Name), []byte(envVar.Value), os.ModePerm) 20 | if err != nil { 21 | return err 22 | } 23 | } 24 | return nil 25 | } 26 | 27 | type envVariable struct { 28 | Name string `json:"name" toml:"name"` 29 | Value string `json:"value" toml:"value"` 30 | } 31 | 32 | type buildEnvVariable struct { 33 | Env []envVariable `toml:"env"` 34 | } 35 | 36 | type envBuildVariable struct { 37 | Env []envVariable 38 | } 39 | 40 | func (a *envBuildVariable) UnmarshalTOML(f interface{}) error { 41 | var ( 42 | env []envVariable 43 | err error 44 | ) 45 | switch v := f.(type) { 46 | case map[string]interface{}: 47 | if envs, ok := v["build"].([]map[string]interface{}); ok { 48 | env, err = buildEnv(envs) 49 | if err != nil { 50 | return err 51 | } 52 | } 53 | case []map[string]interface{}: 54 | env, err = buildEnv(v) 55 | if err != nil { 56 | return err 57 | } 58 | default: 59 | return errors.New("environment variables in project descriptor could not be parsed") 60 | } 61 | a.Env = env 62 | return nil 63 | } 64 | 65 | func buildEnv(v []map[string]interface{}) ([]envVariable, error) { 66 | var e []envVariable 67 | for _, env := range v { 68 | if name, nameOk := env["name"].(string); nameOk { 69 | if value, valueOk := env["value"].(string); valueOk { 70 | e = append(e, envVariable{ 71 | Name: name, 72 | Value: value, 73 | }) 74 | } else { 75 | return nil, errors.Errorf("environment variable '%s' is not a string value", name) 76 | } 77 | } else { 78 | return nil, errors.New("environment variable 'name' is not a string") 79 | } 80 | } 81 | return e, nil 82 | } 83 | -------------------------------------------------------------------------------- /pkg/cnb/platform_env_vars_setup.go: -------------------------------------------------------------------------------- 1 | package cnb 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 8 | ) 9 | 10 | func SetupPlatformEnvVars(dir string) error { 11 | var envVars []envVariable 12 | 13 | for _, envVar := range os.Environ() { 14 | eqPos := strings.Index(envVar, "=") 15 | if eqPos < 0 { 16 | continue 17 | } 18 | 19 | key := envVar[:eqPos] 20 | if !strings.HasPrefix(key, v1alpha2.PlatformEnvVarPrefix) { 21 | continue 22 | } 23 | 24 | val := envVar[eqPos+1:] 25 | envVars = append(envVars, envVariable{ 26 | Name: key[len(v1alpha2.PlatformEnvVarPrefix):], 27 | Value: val, 28 | }) 29 | } 30 | 31 | return serializeEnvVars(envVars, dir) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/cnb/platform_env_vars_setup_test.go: -------------------------------------------------------------------------------- 1 | package cnb_test 2 | 3 | import ( 4 | "os" 5 | "path" 6 | "testing" 7 | 8 | "github.com/sclevine/spec" 9 | "github.com/stretchr/testify/require" 10 | 11 | "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 12 | "github.com/pivotal/kpack/pkg/cnb" 13 | ) 14 | 15 | func TestPlatformEnvVarsSetup(t *testing.T) { 16 | spec.Run(t, "PlatformEnvVarsSetup", testPlatformEnvVarsSetup) 17 | } 18 | 19 | func testPlatformEnvVarsSetup(t *testing.T, when spec.G, it spec.S) { 20 | var ( 21 | testVolume string 22 | platformEnv map[string]string 23 | ) 24 | 25 | it.Before(func() { 26 | var err error 27 | testVolume, err = os.MkdirTemp("", "permission") 28 | require.NoError(t, err) 29 | 30 | platformEnv = map[string]string{ 31 | "keyA": "valueA", 32 | "keyB": "valueB", 33 | "keyC": "foo=bar", 34 | "keyD": "", 35 | } 36 | 37 | for k, v := range platformEnv { 38 | os.Setenv(v1alpha2.PlatformEnvVarPrefix+k, v) 39 | } 40 | }) 41 | 42 | it.After(func() { 43 | for k := range platformEnv { 44 | os.Unsetenv(v1alpha2.PlatformEnvVarPrefix + k) 45 | } 46 | os.RemoveAll(testVolume) 47 | }) 48 | 49 | when("#setup", func() { 50 | it("writes all env var files to the platform dir", func() { 51 | err := cnb.SetupPlatformEnvVars(testVolume) 52 | require.NoError(t, err) 53 | 54 | for k, v := range platformEnv { 55 | checkEnvVar(t, testVolume, k, v) 56 | } 57 | }) 58 | }) 59 | } 60 | 61 | func checkEnvVar(t *testing.T, testVolume, key, value string) { 62 | require.FileExists(t, path.Join(testVolume, "env", key)) 63 | buf, err := os.ReadFile(path.Join(testVolume, "env", key)) 64 | require.NoError(t, err) 65 | require.Equal(t, value, string(buf)) 66 | } 67 | -------------------------------------------------------------------------------- /pkg/cnb/remote_buildpack_metadata.go: -------------------------------------------------------------------------------- 1 | package cnb 2 | 3 | import ( 4 | ggcrv1 "github.com/google/go-containerregistry/pkg/v1" 5 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 6 | "github.com/pivotal/kpack/pkg/registry" 7 | k8sv1 "k8s.io/api/core/v1" 8 | ) 9 | 10 | type RemoteBuildpackInfo struct { 11 | BuildpackInfo DescriptiveBuildpackInfo 12 | Layers []buildpackLayer 13 | } 14 | 15 | func (i RemoteBuildpackInfo) Optional(optional bool) RemoteBuildpackRef { 16 | return RemoteBuildpackRef{ 17 | DescriptiveBuildpackInfo: i.BuildpackInfo, 18 | Optional: optional, 19 | Layers: i.Layers, 20 | } 21 | } 22 | 23 | type RemoteBuildpackRef struct { 24 | DescriptiveBuildpackInfo DescriptiveBuildpackInfo 25 | Optional bool 26 | Layers []buildpackLayer 27 | } 28 | 29 | func (r RemoteBuildpackRef) buildpackRef() corev1alpha1.BuildpackRef { 30 | return corev1alpha1.BuildpackRef{ 31 | BuildpackInfo: r.DescriptiveBuildpackInfo.BuildpackInfo, 32 | Optional: r.Optional, 33 | } 34 | } 35 | 36 | type buildpackLayer struct { 37 | v1Layer ggcrv1.Layer 38 | BuildpackInfo DescriptiveBuildpackInfo 39 | BuildpackLayerInfo BuildpackLayerInfo 40 | } 41 | 42 | type K8sRemoteBuildpack struct { 43 | Buildpack corev1alpha1.BuildpackStatus 44 | SecretRef registry.SecretRef 45 | source k8sv1.ObjectReference 46 | } 47 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 4 | 5 | type Config struct { 6 | SystemNamespace string `json:"systemNamespace"` 7 | SystemServiceAccount string `json:"systemServiceAccount"` 8 | 9 | EnablePriorityClasses bool `json:"enablePriorityClasses"` 10 | MaximumPlatformApiVersion string `json:"maximumPlatformApiVersion"` 11 | SshTrustUnknownHosts bool `json:"sshTrustUnknownHosts"` 12 | ScalingFactor int `json:"scalingFactor"` 13 | } 14 | 15 | type FeatureFlags struct { 16 | InjectedSidecarSupport bool `json:"injectedSidecarSupport"` 17 | GenerateSlsaAttestation bool `json:"generateSlsaAttestation"` 18 | } 19 | 20 | type Images struct { 21 | BuildInitImage string `json:"buildInitImage"` 22 | BuildWaiterImage string `json:"buildWaiterImage"` 23 | CompletionImage string `json:"completionImage"` 24 | RebaseImage string `json:"rebaseImage"` 25 | } 26 | 27 | // TODO: evaluate if we can move the lifecycle_provider stuff out of this config package 28 | // Ideally v1alpha2.BuildPodImages should either just use config.Images directly or be an alias to it. However this 29 | // doesn't work right now because lifecycle_provider.go imports pkg/cnb which imports pkg/apis/build/v1alpha2 and 30 | // thus creating an import cycle. 31 | func (i *Images) ToBuildPodImages() v1alpha2.BuildPodImages { 32 | return v1alpha2.BuildPodImages{ 33 | BuildInitImage: i.BuildInitImage, 34 | BuildWaiterImage: i.BuildWaiterImage, 35 | CompletionImage: i.CompletionImage, 36 | RebaseImage: i.RebaseImage, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pkg/cosign/testing/test_util.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "context" 5 | "crypto" 6 | "testing" 7 | 8 | cosignVerify "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" 9 | "github.com/sigstore/cosign/v2/pkg/cosign" 10 | "github.com/sigstore/cosign/v2/pkg/signature" 11 | "github.com/stretchr/testify/require" 12 | corev1 "k8s.io/api/core/v1" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | 15 | "github.com/pivotal/kpack/pkg/secret" 16 | ) 17 | 18 | func GenerateFakeKeyPair(t *testing.T, secretName string, secretNamespace string, password string, annotations map[string]string) corev1.Secret { 19 | t.Helper() 20 | 21 | passFunc := func(_ bool) ([]byte, error) { 22 | return []byte(password), nil 23 | } 24 | 25 | keys, err := cosign.GenerateKeyPair(passFunc) 26 | require.NoError(t, err) 27 | 28 | data := map[string][]byte{ 29 | secret.CosignSecretPublicKey: keys.PublicBytes, 30 | secret.CosignSecretPrivateKey: keys.PrivateBytes, 31 | secret.CosignSecretPassword: []byte(password), 32 | } 33 | 34 | secret := corev1.Secret{ 35 | ObjectMeta: metav1.ObjectMeta{ 36 | Name: secretName, 37 | Namespace: secretNamespace, 38 | Annotations: annotations, 39 | }, 40 | Data: data, 41 | } 42 | 43 | return secret 44 | } 45 | 46 | func Verify(t *testing.T, keyRef, imageRef string, annotations map[string]interface{}) error { 47 | t.Helper() 48 | 49 | cmd := cosignVerify.VerifyCommand{ 50 | KeyRef: keyRef, 51 | Annotations: signature.AnnotationsMap{Annotations: annotations}, 52 | CheckClaims: true, 53 | HashAlgorithm: crypto.SHA256, 54 | IgnoreTlog: true, 55 | } 56 | 57 | args := []string{imageRef} 58 | 59 | return cmd.Exec(context.Background(), args) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/dockercreds/access_checker.go: -------------------------------------------------------------------------------- 1 | package dockercreds 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/google/go-containerregistry/pkg/authn" 9 | "github.com/google/go-containerregistry/pkg/name" 10 | "github.com/google/go-containerregistry/pkg/v1/remote" 11 | "github.com/google/go-containerregistry/pkg/v1/remote/transport" 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | func VerifyWriteAccess(keychain authn.Keychain, tag string) error { 16 | ref, err := name.ParseReference(tag, name.WeakValidation) 17 | if err != nil { 18 | return errors.Wrapf(err, "Error parsing reference %q", tag) 19 | } 20 | 21 | if err = remote.CheckPushPermission(ref, keychain, http.DefaultTransport); err != nil { 22 | return diagnoseIfTransportError(err) 23 | } 24 | 25 | return nil 26 | } 27 | 28 | func VerifyReadAccess(keychain authn.Keychain, tag string) error { 29 | ref, err := name.ParseReference(tag, name.WeakValidation) 30 | if err != nil { 31 | return errors.Wrapf(err, "Error parsing reference %q", tag) 32 | } 33 | 34 | if _, err = remote.Get(ref, remote.WithAuthFromKeychain(keychain), remote.WithTransport(http.DefaultTransport)); err != nil { 35 | return diagnoseIfTransportError(err) 36 | } 37 | 38 | return nil 39 | } 40 | 41 | func diagnoseIfTransportError(err error) error { 42 | if err == nil { 43 | return nil 44 | } 45 | 46 | // transport.Error implements error to support the following error specification: 47 | // https://github.com/docker/distribution/blob/master/docs/spec/api.md#errors 48 | transportError, ok := err.(*transport.Error) 49 | if !ok { 50 | return err 51 | } 52 | 53 | // handle artifactory. refer test case 54 | if transportError.StatusCode == 401 { 55 | return errors.New(string(transport.UnauthorizedErrorCode)) 56 | } 57 | 58 | if len(transportError.Errors) == 0 { 59 | return err 60 | } 61 | 62 | var messageBuilder strings.Builder 63 | for _, diagnosticError := range transportError.Errors { 64 | messageBuilder.WriteString(fmt.Sprintf("%s. ", diagnosticError.Message)) 65 | } 66 | 67 | return errors.New(messageBuilder.String()) 68 | } 69 | -------------------------------------------------------------------------------- /pkg/dockercreds/cached_keychain.go: -------------------------------------------------------------------------------- 1 | package dockercreds 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/google/go-containerregistry/pkg/authn" 8 | "github.com/pivotal/kpack/pkg/registry" 9 | ) 10 | 11 | type cacheKey string 12 | 13 | type cachedKeychainFactory struct { 14 | keychainFactory registry.KeychainFactory 15 | cache map[cacheKey]authn.Keychain 16 | } 17 | 18 | func NewCachedKeychainFactory(keychainFactory registry.KeychainFactory) registry.KeychainFactory { 19 | return &cachedKeychainFactory{ 20 | keychainFactory: keychainFactory, 21 | cache: make(map[cacheKey]authn.Keychain), 22 | } 23 | } 24 | 25 | func (f *cachedKeychainFactory) KeychainForSecretRef(ctx context.Context, secretRef registry.SecretRef) (authn.Keychain, error) { 26 | key := makeKey(secretRef) 27 | if keychain, found := f.cache[key]; found { 28 | return keychain, nil 29 | } 30 | 31 | keychain, err := f.keychainFactory.KeychainForSecretRef(ctx, secretRef) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | f.cache[key] = keychain 37 | return keychain, nil 38 | } 39 | 40 | func makeKey(secretRef registry.SecretRef) cacheKey { 41 | return cacheKey(fmt.Sprintf("%v/%v", secretRef.Namespace, secretRef.ServiceAccount)) 42 | } 43 | -------------------------------------------------------------------------------- /pkg/dockercreds/docker_creds.go: -------------------------------------------------------------------------------- 1 | package dockercreds 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/google/go-containerregistry/pkg/authn" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | type DockerCreds map[string]authn.AuthConfig 15 | 16 | func (c DockerCreds) Resolve(reg authn.Resource) (authn.Authenticator, error) { 17 | for registry, entry := range c { 18 | matcher := RegistryMatcher{Registry: registry} 19 | if matcher.Match(reg.RegistryStr()) { 20 | return authn.FromConfig(entry), nil 21 | } 22 | } 23 | 24 | // Fallback on anonymous. 25 | return authn.Anonymous, nil 26 | } 27 | 28 | func (c DockerCreds) Save(path string) error { 29 | err := os.MkdirAll(filepath.Dir(path), 0777) 30 | if err != nil { 31 | return errors.Wrapf(err, "error creating %s", filepath.Dir(path)) 32 | } 33 | 34 | configJson := dockerConfigJson{ 35 | Auths: c, 36 | } 37 | 38 | fh, err := os.Create(path) 39 | if err != nil { 40 | return err 41 | } 42 | defer fh.Close() 43 | return json.NewEncoder(fh).Encode(configJson) 44 | } 45 | 46 | func (c DockerCreds) Append(a DockerCreds) (DockerCreds, error) { 47 | if c == nil { 48 | return a, nil 49 | } else if a == nil { 50 | return c, nil 51 | } 52 | 53 | for k, v := range a { 54 | if contains, err := c.contains(k); err != nil { 55 | return nil, err 56 | } else if !contains { 57 | c[k] = v 58 | } 59 | } 60 | 61 | return c, nil 62 | } 63 | 64 | func (c DockerCreds) contains(reg string) (bool, error) { 65 | if !strings.HasPrefix(reg, "http://") && !strings.HasPrefix(reg, "https://") { 66 | reg = "//" + reg 67 | } 68 | 69 | u, err := url.Parse(reg) 70 | if err != nil { 71 | return false, err 72 | } 73 | 74 | for existingRegistry := range c { 75 | matcher := RegistryMatcher{Registry: existingRegistry} 76 | if matcher.Match(u.Host) { 77 | return true, nil 78 | } 79 | } 80 | 81 | return false, nil 82 | } 83 | 84 | type dockerConfigJson struct { 85 | Auths DockerCreds `json:"auths"` 86 | } 87 | -------------------------------------------------------------------------------- /pkg/dockercreds/k8sdockercreds/azurecredentialhelperfix/keychain.go: -------------------------------------------------------------------------------- 1 | package azurecredentialhelperfix 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/google/go-containerregistry/pkg/authn" 8 | "github.com/google/go-containerregistry/pkg/name" 9 | credentialprovider "github.com/vdemeester/k8s-pkg-credentialprovider" 10 | ) 11 | 12 | var ( 13 | once sync.Once 14 | keyring credentialprovider.DockerKeyring 15 | ) 16 | 17 | func AzureFileKeychain() authn.Keychain { 18 | once.Do(func() { 19 | keyring = credentialprovider.NewDockerKeyring() 20 | }) 21 | 22 | return &keychain{keyring: keyring} 23 | } 24 | 25 | // Copied from https://github.com/google/go-containerregistry/blob/d9bfbcb99e526b2a9417160e209b816e1b1fb6bd/pkg/authn/k8schain/k8schain.go#L141 26 | type lazyProvider struct { 27 | kc *keychain 28 | image string 29 | } 30 | 31 | // Authorization implements Authenticator. 32 | func (lp lazyProvider) Authorization() (*authn.AuthConfig, error) { 33 | creds, found := lp.kc.keyring.Lookup(lp.image) 34 | if !found || len(creds) < 1 { 35 | return nil, fmt.Errorf("keychain returned no credentials for %q", lp.image) 36 | } 37 | authConfig := creds[0] 38 | return &authn.AuthConfig{ 39 | Username: authConfig.Username, 40 | Password: authConfig.Password, 41 | Auth: authConfig.Auth, 42 | IdentityToken: authConfig.IdentityToken, 43 | RegistryToken: authConfig.RegistryToken, 44 | }, nil 45 | } 46 | 47 | type keychain struct { 48 | keyring credentialprovider.DockerKeyring 49 | } 50 | 51 | // Resolve implements authn.Keychain 52 | func (kc *keychain) Resolve(target authn.Resource) (authn.Authenticator, error) { 53 | var image string 54 | if repo, ok := target.(name.Repository); ok { 55 | image = repo.String() 56 | } else { 57 | // Lookup expects an image reference and we only have a registry. 58 | image = target.RegistryStr() + "/foo/bar" 59 | } 60 | 61 | if creds, found := kc.keyring.Lookup(image); !found || len(creds) < 1 { 62 | return authn.Anonymous, nil 63 | } 64 | return lazyProvider{ 65 | kc: kc, 66 | image: image, 67 | }, nil 68 | } 69 | -------------------------------------------------------------------------------- /pkg/dockercreds/k8sdockercreds/azurecredentialhelperfix/setup.go: -------------------------------------------------------------------------------- 1 | package azurecredentialhelperfix 2 | 3 | import ( 4 | _ "github.com/vdemeester/k8s-pkg-credentialprovider/azure" 5 | 6 | "os" 7 | 8 | "github.com/spf13/pflag" 9 | ) 10 | 11 | func init() { 12 | pflag.Set("azure-container-registry-config", os.Getenv("AZURE_CONTAINER_REGISTRY_CONFIG")) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/dockercreds/match.go: -------------------------------------------------------------------------------- 1 | package dockercreds 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/google/go-containerregistry/pkg/authn" 7 | "github.com/google/go-containerregistry/pkg/name" 8 | ) 9 | 10 | var registryDomains = []string{ 11 | // Allow naked domains 12 | "%s", 13 | // Allow scheme-prefixed. 14 | "https://%s", 15 | "http://%s", 16 | // Allow scheme-prefixes with version in url path. 17 | "https://%s/v1/", 18 | "http://%s/v1/", 19 | "https://%s/v2/", 20 | "http://%s/v2/", 21 | } 22 | 23 | type RegistryMatcher struct { 24 | Registry string 25 | } 26 | 27 | func (m RegistryMatcher) Match(reg string) bool { 28 | for _, format := range registryDomains { 29 | if fmt.Sprintf(format, registryString(reg)) == m.Registry { 30 | return true 31 | } 32 | } 33 | return false 34 | } 35 | 36 | func registryString(reg string) string { 37 | if reg == name.DefaultRegistry { 38 | return authn.DefaultAuthKey 39 | } 40 | return reg 41 | } 42 | -------------------------------------------------------------------------------- /pkg/dockercreds/match_test.go: -------------------------------------------------------------------------------- 1 | package dockercreds 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sclevine/spec" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestMatch(t *testing.T) { 11 | spec.Run(t, "RegistryMatch", testRegistryMatch) 12 | } 13 | 14 | func testRegistryMatch(t *testing.T, when spec.G, it spec.S) { 15 | when("#Match", func() { 16 | for _, regFormat := range []string{ 17 | // Allow naked domains 18 | "reg.io", 19 | // Allow scheme-prefixed. 20 | "https://reg.io", 21 | "http://reg.io", 22 | // Allow scheme-prefixes with version in url path. 23 | "https://reg.io/v1/", 24 | "http://reg.io/v1/", 25 | "https://reg.io/v2/", 26 | "http://reg.io/v2/", 27 | } { 28 | it("matches format "+regFormat, func() { 29 | matcher := RegistryMatcher{Registry: regFormat} 30 | assert.True(t, matcher.Match("reg.io")) 31 | }) 32 | 33 | it("does not match other registries with "+regFormat, func() { 34 | matcher := RegistryMatcher{Registry: regFormat} 35 | assert.False(t, matcher.Match("gcr.io")) 36 | }) 37 | } 38 | 39 | it("only matches on fully qualified dockerhub references", func() { 40 | matcher := RegistryMatcher{Registry: "https://index.docker.io/v1/"} 41 | assert.True(t, matcher.Match("index.docker.io")) 42 | 43 | matcher = RegistryMatcher{Registry: "index.docker.io"} 44 | assert.False(t, matcher.Match("index.docker.io")) 45 | }) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /pkg/dockercreds/volume_secret_keychain.go: -------------------------------------------------------------------------------- 1 | package dockercreds 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/google/go-containerregistry/pkg/authn" 7 | ) 8 | 9 | const ( 10 | SecretFilePathEnv = "CREDENTIAL_PROVIDER_SECRET_PATH" 11 | ) 12 | 13 | func NewVolumeSecretKeychain() (authn.Keychain, error) { 14 | secretFolder, ok := os.LookupEnv(SecretFilePathEnv) 15 | if !ok { 16 | return DockerCreds{}, nil 17 | } 18 | 19 | return ParseDockerConfigSecret(secretFolder) 20 | } 21 | -------------------------------------------------------------------------------- /pkg/duckbuilder/duck_builder.go: -------------------------------------------------------------------------------- 1 | package duckbuilder 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | 7 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 8 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 9 | ) 10 | 11 | type DuckBuilder struct { 12 | metav1.TypeMeta `json:",inline"` 13 | metav1.ObjectMeta `json:"metadata,omitempty"` 14 | 15 | Spec DuckBuilderSpec `json:"spec"` 16 | Status buildapi.BuilderStatus `json:"status"` 17 | } 18 | 19 | func (b *DuckBuilder) GetName() string { 20 | return b.Name 21 | } 22 | 23 | func (b *DuckBuilder) GetNamespace() string { 24 | return b.Namespace 25 | } 26 | 27 | func (b *DuckBuilder) GetKind() string { 28 | return b.Kind 29 | } 30 | 31 | type DuckBuilderSpec struct { 32 | ImagePullSecrets []v1.LocalObjectReference 33 | } 34 | 35 | func (b *DuckBuilder) Ready() bool { 36 | return b.Status.GetCondition(corev1alpha1.ConditionReady).IsTrue() && 37 | (b.Generation == b.Status.ObservedGeneration) 38 | } 39 | 40 | func (b *DuckBuilder) UpToDate() bool { 41 | return b.Status.GetCondition(buildapi.ConditionUpToDate).IsTrue() && 42 | (b.Generation == b.Status.ObservedGeneration) 43 | } 44 | 45 | func (b *DuckBuilder) BuildBuilderSpec() corev1alpha1.BuildBuilderSpec { 46 | return corev1alpha1.BuildBuilderSpec{ 47 | Image: b.Status.LatestImage, 48 | ImagePullSecrets: b.Spec.ImagePullSecrets, 49 | } 50 | } 51 | 52 | func (b *DuckBuilder) BuildpackMetadata() corev1alpha1.BuildpackMetadataList { 53 | return b.Status.BuilderMetadata 54 | } 55 | 56 | func (b *DuckBuilder) RunImage() string { 57 | return b.Status.Stack.RunImage 58 | } 59 | 60 | func (b *DuckBuilder) LifecycleVersion() string { 61 | return b.Status.Lifecycle.Version 62 | } 63 | 64 | func (b *DuckBuilder) ConditionReadyMessage() string { 65 | condition := b.Status.GetCondition(corev1alpha1.ConditionReady) 66 | if condition == nil { 67 | return "" 68 | } 69 | 70 | return condition.Message 71 | } 72 | -------------------------------------------------------------------------------- /pkg/duckprovisionedserviceable/fake/fakeprovisionedservice.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | "k8s.io/apimachinery/pkg/runtime" 7 | "k8s.io/apimachinery/pkg/runtime/schema" 8 | ) 9 | 10 | type FakeProvisionedService struct { 11 | metav1.TypeMeta `json:",inline"` 12 | metav1.ObjectMeta `json:"metadata,omitempty"` 13 | 14 | Spec ProvisionedServiceSpec `json:"spec,omitempty"` 15 | Status ProvisionedServiceStatus `json:"status,omitempty"` 16 | } 17 | 18 | func (ps *FakeProvisionedService) DeepCopyObject() runtime.Object { 19 | return &FakeProvisionedService{ 20 | TypeMeta: ps.TypeMeta, 21 | ObjectMeta: ps.ObjectMeta, 22 | Spec: ps.Spec, 23 | Status: ps.Status, 24 | } 25 | } 26 | 27 | func (ps *FakeProvisionedService) GetGroupVersionKind() schema.GroupVersionKind { 28 | return ps.GroupVersionKind() 29 | } 30 | 31 | type ProvisionedServiceSpec struct { 32 | //random spec 33 | DatabaseSize string `json:"databaseSize,omitempty"` 34 | } 35 | 36 | type ProvisionedServiceStatus struct { 37 | Binding corev1.LocalObjectReference `json:"binding,omitempty"` 38 | } 39 | -------------------------------------------------------------------------------- /pkg/duckprovisionedserviceable/provisionedservicable.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 the original author or authors. 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 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package duckprovisionedserviceable 15 | 16 | import ( 17 | corev1 "k8s.io/api/core/v1" 18 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 | ) 20 | 21 | type ProvisionedServicable struct { 22 | metav1.TypeMeta `json:",inline"` 23 | metav1.ObjectMeta `json:"metadata,omitempty"` 24 | 25 | Status ProvisionedServicableStatus `json:"status"` 26 | } 27 | 28 | type ProvisionedServicableStatus struct { 29 | Binding *corev1.LocalObjectReference `json:"binding,omitempty"` 30 | } 31 | -------------------------------------------------------------------------------- /pkg/flaghelpers/credential_flags.go: -------------------------------------------------------------------------------- 1 | package flaghelpers 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | type CredentialsFlags []string 10 | 11 | func (i *CredentialsFlags) String() string { 12 | builder := strings.Builder{} 13 | for _, v := range *i { 14 | builder.WriteString(v) 15 | } 16 | return builder.String() 17 | } 18 | 19 | func (i *CredentialsFlags) Set(value string) error { 20 | *i = append(*i, value) 21 | return nil 22 | } 23 | 24 | func GetEnvBool(key string, defaultValue bool) bool { 25 | s := os.Getenv(key) 26 | if s == "" { 27 | return defaultValue 28 | } 29 | v, err := strconv.ParseBool(s) 30 | if err != nil { 31 | return defaultValue 32 | } 33 | return v 34 | } 35 | 36 | 37 | func GetEnvInt(key string, defaultValue int) int { 38 | s := os.Getenv(key) 39 | v, err := strconv.Atoi(s) 40 | if err != nil { 41 | return defaultValue 42 | } 43 | return v 44 | } 45 | -------------------------------------------------------------------------------- /pkg/git/resolver.go: -------------------------------------------------------------------------------- 1 | package git 2 | 3 | import ( 4 | "context" 5 | 6 | k8sclient "k8s.io/client-go/kubernetes" 7 | 8 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 9 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 10 | ) 11 | 12 | type Resolver struct { 13 | remoteGitResolver remoteGitResolver 14 | gitKeychain *k8sGitKeychain 15 | } 16 | 17 | func NewResolver(k8sClient k8sclient.Interface, sshTrustUnknownHosts bool) *Resolver { 18 | return &Resolver{ 19 | remoteGitResolver: remoteGitResolver{}, 20 | gitKeychain: newK8sGitKeychain(k8sClient, sshTrustUnknownHosts), 21 | } 22 | } 23 | 24 | func (r *Resolver) Resolve(ctx context.Context, sourceResolver *buildapi.SourceResolver) (corev1alpha1.ResolvedSourceConfig, error) { 25 | auth, err := r.gitKeychain.Resolve(ctx, sourceResolver.Namespace, sourceResolver.Spec.ServiceAccountName, *sourceResolver.Spec.Source.Git) 26 | if err != nil { 27 | return corev1alpha1.ResolvedSourceConfig{}, err 28 | } 29 | 30 | return r.remoteGitResolver.Resolve(auth, sourceResolver.Spec.Source) 31 | } 32 | 33 | func (*Resolver) CanResolve(sourceResolver *buildapi.SourceResolver) bool { 34 | return sourceResolver.IsGit() 35 | } 36 | -------------------------------------------------------------------------------- /pkg/git/url_parser.go: -------------------------------------------------------------------------------- 1 | package git 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | var shortScpRegex = regexp.MustCompile(`^(ssh://)?(.*)@([[:alnum:]\.-]+):(.*)$`) 9 | 10 | // ParseURL converts a short scp-like SSH syntax to a proper SSH URL. 11 | // Git's ssh protocol supports a URL like user@hostname:path syntax, which is 12 | // not a valid ssh url but is inherited from scp. Because the library we 13 | // use for git relies on the Golang SSH support, we need to convert it to a 14 | // proper SSH URL. 15 | // See https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols 16 | func parseURL(url string) string { 17 | parts := shortScpRegex.FindStringSubmatch(url) 18 | if len(parts) == 0 { 19 | return url 20 | } 21 | if parts[1] == "ssh://" { 22 | return url 23 | } 24 | 25 | return fmt.Sprintf("ssh://%v@%v/%v", parts[2], parts[3], parts[4]) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/git/url_parser_test.go: -------------------------------------------------------------------------------- 1 | package git 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sclevine/spec" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestParseURL(t *testing.T) { 11 | spec.Focus(t, "Test Parse Git URL", testParseURL) 12 | } 13 | 14 | func testParseURL(t *testing.T, when spec.G, it spec.S) { 15 | type entry struct { 16 | url string 17 | expected string 18 | } 19 | 20 | testURLs := func(table []entry) { 21 | for _, e := range table { 22 | assert.Equal(t, e.expected, parseURL(e.url)) 23 | } 24 | } 25 | 26 | // https: //stackoverflow.com/questions/31801271/what-are-the-supported-git-url-formats 27 | when("using the local protcol", func() { 28 | it("parses the url correctly", func() { 29 | testURLs([]entry{ 30 | {url: "/path/to/repo.git", expected: "/path/to/repo.git"}, 31 | {url: "file:///path/to/repo.git", expected: "file:///path/to/repo.git"}, 32 | }) 33 | }) 34 | }) 35 | when("using the https protcol", func() { 36 | it("parses the url correctly", func() { 37 | testURLs([]entry{ 38 | {url: "http://host.xz/path/to/repo.git", expected: "http://host.xz/path/to/repo.git"}, 39 | {url: "https://host.xz/path/to/repo.git", expected: "https://host.xz/path/to/repo.git"}, 40 | }) 41 | }) 42 | }) 43 | when("using the ssh protcol", func() { 44 | it("parses the url correctly", func() { 45 | testURLs([]entry{ 46 | {url: "ssh://user@host.xz:port/path/to/repo.git/", expected: "ssh://user@host.xz:port/path/to/repo.git/"}, 47 | {url: "ssh://user@host.xz/path/to/repo.git/", expected: "ssh://user@host.xz/path/to/repo.git/"}, 48 | {url: "user@host.xz:path/to/repo.git", expected: "ssh://user@host.xz/path/to/repo.git"}, 49 | }) 50 | }) 51 | }) 52 | when("using the git protcol", func() { 53 | it("parses the url correctly", func() { 54 | testURLs([]entry{ 55 | {url: "git://host.xz/path/to/repo.git", expected: "git://host.xz/path/to/repo.git"}, 56 | }) 57 | }) 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /pkg/logs/watch_build.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/runtime" 9 | "k8s.io/apimachinery/pkg/watch" 10 | 11 | "github.com/pivotal/kpack/pkg/client/clientset/versioned" 12 | ) 13 | 14 | type watchOneBuild struct { 15 | buildName string 16 | kpackClient versioned.Interface 17 | namespace string 18 | context context.Context 19 | } 20 | 21 | func (l *watchOneBuild) Watch(options v1.ListOptions) (watch.Interface, error) { 22 | options.FieldSelector = fmt.Sprintf("metadata.name=%s", l.buildName) 23 | 24 | return l.kpackClient.KpackV1alpha1().Builds(l.namespace).Watch(l.context, options) 25 | } 26 | 27 | func (l *watchOneBuild) List(options v1.ListOptions) (runtime.Object, error) { 28 | options.FieldSelector = fmt.Sprintf("metadata.name=%s", l.buildName) 29 | 30 | return l.kpackClient.KpackV1alpha1().Builds(l.namespace).List(l.context, options) 31 | } 32 | -------------------------------------------------------------------------------- /pkg/logs/watch_image.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/watch" 9 | 10 | "github.com/pivotal/kpack/pkg/apis/build/v1alpha1" 11 | "github.com/pivotal/kpack/pkg/client/clientset/versioned" 12 | ) 13 | 14 | type watchOneImage struct { 15 | kpackClient versioned.Interface 16 | image *v1alpha1.Image 17 | ctx context.Context 18 | } 19 | 20 | func (w watchOneImage) Watch(options v1.ListOptions) (watch.Interface, error) { 21 | options.FieldSelector = fmt.Sprintf("metadata.name=%s", w.image.Name) 22 | return w.kpackClient.KpackV1alpha1().Images(w.image.Namespace).Watch(w.ctx, options) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/notary/testdata/notary-no-key/password: -------------------------------------------------------------------------------- 1 | Password1! -------------------------------------------------------------------------------- /pkg/notary/testdata/notary/2a7690c4784f2c62770a27e1ae807ecf1f0bd0501c11bb68a0bab2e8ccdee24f.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | gun: example-registry.io/test 3 | role: targets 4 | 5 | MIHuMEkGCSqGSIb3DQEFDTA8MBsGCSqGSIb3DQEFDDAOBAgPOYUCvuGVGgICCAAw 6 | HQYJYIZIAWUDBAEqBBCvDdvTSSKixpoJIQIRkbMRBIGgWDLMKQxBM4MUia+g4mbA 7 | tRuelUlczekboHaCaAaPqy6g82/2oReJ9b1lTXnKOOFIopJbeRvHdoaRW8E5Dsi4 8 | /CPS+IB78v0Z65xPbmXD4MWtXBwsBwX27x7mm9ngi/VOO68r0Q5u1RsG84ng3UjF 9 | 2s6bgBWzzBKmanWCg4kbARmOu/SopzjqFtJqSf6vTBVMU67jMybMUIcLRO1JwTuo 10 | zQ== 11 | -----END ENCRYPTED PRIVATE KEY----- 12 | -------------------------------------------------------------------------------- /pkg/notary/testdata/notary/password: -------------------------------------------------------------------------------- 1 | Password1! -------------------------------------------------------------------------------- /pkg/notary/testdata/report-multiple-gun.toml: -------------------------------------------------------------------------------- 1 | [image] 2 | tags = ["example-registry.io/test:latest", "some-other-registry.io/test:other-tag"] 3 | digest = "sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65" -------------------------------------------------------------------------------- /pkg/notary/testdata/report.toml: -------------------------------------------------------------------------------- 1 | [image] 2 | tags = ["example-registry.io/test:latest", "example-registry.io/test:other-tag"] 3 | digest = "sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65" -------------------------------------------------------------------------------- /pkg/reconciler/build/sort.go: -------------------------------------------------------------------------------- 1 | package build 2 | 3 | import ( 4 | _ "strconv" 5 | 6 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 7 | ) 8 | 9 | type ByCreationTimestamp []*buildapi.Build 10 | 11 | func (o ByCreationTimestamp) Len() int { return len(o) } 12 | func (o ByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } 13 | 14 | func (o ByCreationTimestamp) Less(i, j int) bool { 15 | if o[i].ObjectMeta.CreationTimestamp.IsZero() && !o[j].ObjectMeta.CreationTimestamp.IsZero() { 16 | return false 17 | } 18 | if !o[i].ObjectMeta.CreationTimestamp.IsZero() && o[j].ObjectMeta.CreationTimestamp.IsZero() { 19 | return true 20 | } 21 | 22 | if o[i].ObjectMeta.CreationTimestamp.Equal(&o[j].ObjectMeta.CreationTimestamp) { 23 | return true 24 | } 25 | return o[i].ObjectMeta.CreationTimestamp.Before(&o[j].ObjectMeta.CreationTimestamp) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/reconciler/filter.go: -------------------------------------------------------------------------------- 1 | package reconciler 2 | 3 | import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 4 | 5 | func FilterDeletionTimestamp(obj interface{}) bool { 6 | object, ok := obj.(metav1.Object) 7 | if !ok { 8 | return false 9 | } 10 | 11 | return object.GetDeletionTimestamp() == nil 12 | } 13 | -------------------------------------------------------------------------------- /pkg/reconciler/filter_test.go: -------------------------------------------------------------------------------- 1 | package reconciler_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 8 | "github.com/pivotal/kpack/pkg/reconciler" 9 | "github.com/sclevine/spec" 10 | "github.com/stretchr/testify/require" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | ) 13 | 14 | func TestFilterDeletionTimestamp(t *testing.T) { 15 | spec.Run(t, "FilterDeletionTimestamp", testFilterDeletionTimestamp) 16 | } 17 | 18 | func testFilterDeletionTimestamp(t *testing.T, when spec.G, it spec.S) { 19 | when("#FilterDeletionTimestamp", func() { 20 | it("returns true", func() { 21 | require.True(t, reconciler.FilterDeletionTimestamp(&buildapi.Build{})) 22 | }) 23 | 24 | when("object is deleted", func() { 25 | it("returns false", func() { 26 | require.False(t, reconciler.FilterDeletionTimestamp(buildapi.Build{ 27 | ObjectMeta: metav1.ObjectMeta{ 28 | DeletionTimestamp: &metav1.Time{Time: time.Now()}, 29 | }, 30 | })) 31 | }) 32 | }) 33 | 34 | when("not an object", func() { 35 | it("returns false", func() { 36 | require.False(t, reconciler.FilterDeletionTimestamp("not an object")) 37 | }) 38 | }) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/reconciler/image/build_list.go: -------------------------------------------------------------------------------- 1 | package image 2 | 3 | import ( 4 | "sort" 5 | 6 | v1alpha1build "github.com/pivotal/kpack/pkg/reconciler/build" 7 | 8 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 9 | ) 10 | 11 | type buildList struct { 12 | successfulBuilds []*buildapi.Build 13 | failedBuilds []*buildapi.Build 14 | lastBuild *buildapi.Build 15 | } 16 | 17 | func newBuildList(builds []*buildapi.Build) (buildList, error) { 18 | sort.Sort(v1alpha1build.ByCreationTimestamp(builds)) //nobody enforcing this 19 | 20 | buildList := buildList{} 21 | 22 | for _, build := range builds { 23 | if build.IsSuccess() { 24 | buildList.successfulBuilds = append(buildList.successfulBuilds, build) 25 | } else if build.IsFailure() { 26 | buildList.failedBuilds = append(buildList.failedBuilds, build) 27 | } 28 | } 29 | 30 | if len(builds) > 0 { 31 | buildList.lastBuild = builds[len(builds)-1] 32 | } 33 | 34 | return buildList, nil 35 | } 36 | 37 | func (l buildList) NumberFailedBuilds() int64 { 38 | return int64(len(l.failedBuilds)) 39 | } 40 | 41 | func (l buildList) OldestFailure() *buildapi.Build { 42 | return l.failedBuilds[0] 43 | } 44 | 45 | func (l buildList) NumberSuccessfulBuilds() int64 { 46 | return int64(len(l.successfulBuilds)) 47 | } 48 | 49 | func (l buildList) OldestSuccess() *buildapi.Build { 50 | return l.successfulBuilds[0] 51 | } 52 | -------------------------------------------------------------------------------- /pkg/reconciler/network_error_reconciler.go: -------------------------------------------------------------------------------- 1 | package reconciler 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | "knative.dev/pkg/controller" 8 | ) 9 | 10 | type NetworkErrorReconciler struct { 11 | Reconciler controller.Reconciler 12 | } 13 | 14 | func (r *NetworkErrorReconciler) Reconcile(ctx context.Context, key string) error { 15 | if err := r.Reconciler.Reconcile(ctx, key); err != nil { 16 | var networkError *NetworkError 17 | if errors.As(err, &networkError) { 18 | // Re-queue the key if it's a network error. 19 | return err 20 | } 21 | return controller.NewPermanentError(err) 22 | } 23 | return nil 24 | } 25 | 26 | type NetworkError struct { 27 | Err error 28 | } 29 | 30 | func (e *NetworkError) Error() string { 31 | return e.Err.Error() 32 | } 33 | -------------------------------------------------------------------------------- /pkg/reconciler/network_error_reconciler_test.go: -------------------------------------------------------------------------------- 1 | package reconciler 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/pkg/errors" 8 | "github.com/sclevine/spec" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | const ( 13 | networkErrorMsg = "some network problem" 14 | otherErrorMsg = "some other error" 15 | ) 16 | 17 | func TestNetworkErrorReconciler(t *testing.T) { 18 | spec.Run(t, "Network Error Reconciler", testNetworkErrorReconciler) 19 | } 20 | 21 | func testNetworkErrorReconciler(t *testing.T, when spec.G, it spec.S) { 22 | 23 | when("#Reconcile", func() { 24 | when("network error", func() { 25 | subject := &NetworkErrorReconciler{Reconciler: &fakeErrorReconciler{retry: true}} 26 | it("re-throw the error", func() { 27 | err := subject.Reconcile(context.Background(), "whatever") 28 | 29 | require.Error(t, err) 30 | 31 | var networkError *NetworkError 32 | require.True(t, errors.As(err, &networkError)) 33 | require.Equal(t, networkErrorMsg, err.Error()) 34 | }) 35 | }) 36 | 37 | when("other error", func() { 38 | subject := &NetworkErrorReconciler{Reconciler: &fakeErrorReconciler{retry: false}} 39 | it("wraps it as permanent error", func() { 40 | err := subject.Reconcile(context.Background(), "whatever") 41 | 42 | require.Error(t, err) 43 | 44 | var networkError *NetworkError 45 | require.False(t, errors.As(err, &networkError)) 46 | require.Equal(t, otherErrorMsg, err.Error()) 47 | }) 48 | }) 49 | }) 50 | } 51 | 52 | type fakeErrorReconciler struct { 53 | retry bool 54 | } 55 | 56 | func (r *fakeErrorReconciler) Reconcile(ctx context.Context, key string) error { 57 | if r.retry { 58 | err := errors.New(networkErrorMsg) 59 | return &NetworkError{Err: err} 60 | } 61 | err := errors.New(otherErrorMsg) 62 | return err 63 | } 64 | -------------------------------------------------------------------------------- /pkg/reconciler/options.go: -------------------------------------------------------------------------------- 1 | package reconciler 2 | 3 | import ( 4 | "time" 5 | 6 | "go.uber.org/zap" 7 | 8 | "github.com/pivotal/kpack/pkg/client/clientset/versioned" 9 | ) 10 | 11 | type Options struct { 12 | Logger *zap.SugaredLogger 13 | 14 | Client versioned.Interface 15 | ResyncPeriod time.Duration 16 | SourcePollingFrequency time.Duration 17 | BuilderPollingFrequency time.Duration 18 | } 19 | 20 | func (o Options) TrackerResyncPeriod() time.Duration { 21 | return o.ResyncPeriod * 3 22 | } 23 | -------------------------------------------------------------------------------- /pkg/reconciler/sourceresolver/enqueuer.go: -------------------------------------------------------------------------------- 1 | package sourceresolver 2 | 3 | import ( 4 | "time" 5 | 6 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 7 | ) 8 | 9 | type workQueueEnqueuer struct { 10 | enqueueAfter func(obj interface{}, after time.Duration) 11 | delay time.Duration 12 | } 13 | 14 | func (e *workQueueEnqueuer) Enqueue(sr *buildapi.SourceResolver) error { 15 | e.enqueueAfter(sr, 1*time.Minute) 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /pkg/reconciler/sourceresolver/enqueuer_test.go: -------------------------------------------------------------------------------- 1 | package sourceresolver 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/require" 8 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | 10 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 11 | ) 12 | 13 | func TestEnqueueAfter(t *testing.T) { 14 | sourceResolver := &buildapi.SourceResolver{ 15 | ObjectMeta: v1.ObjectMeta{ 16 | Name: "name", 17 | }, 18 | } 19 | 20 | enqueuer := &workQueueEnqueuer{ 21 | delay: time.Minute, 22 | enqueueAfter: func(obj interface{}, after time.Duration) { 23 | require.Equal(t, sourceResolver, obj) 24 | require.Equal(t, after, time.Minute) 25 | }, 26 | } 27 | 28 | err := enqueuer.Enqueue(sourceResolver) 29 | require.NoError(t, err) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/reconciler/testhelpers/fake_builder_creator.go: -------------------------------------------------------------------------------- 1 | package testhelpers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/google/go-containerregistry/pkg/authn" 7 | corev1 "k8s.io/api/core/v1" 8 | 9 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 10 | "github.com/pivotal/kpack/pkg/cnb" 11 | ) 12 | 13 | type FakeBuilderCreator struct { 14 | Record buildapi.BuilderRecord 15 | CreateErr error 16 | ObjectsToTrack []corev1.ObjectReference 17 | 18 | CreateBuilderCalls []CreateBuilderArgs 19 | } 20 | 21 | type CreateBuilderArgs struct { 22 | Context context.Context 23 | BuilderKeychain authn.Keychain 24 | StackKeychain authn.Keychain 25 | Fetcher cnb.RemoteBuildpackFetcher 26 | ClusterStack *buildapi.ClusterStack 27 | ClusterLifecycle *buildapi.ClusterLifecycle 28 | BuilderSpec buildapi.BuilderSpec 29 | SigningSecrets []*corev1.Secret 30 | ResolvedBuilderTag string 31 | } 32 | 33 | func (f *FakeBuilderCreator) CreateBuilder( 34 | ctx context.Context, 35 | builderKeychain authn.Keychain, 36 | stackKeychain authn.Keychain, 37 | lifecycleKeychain authn.Keychain, 38 | fetcher cnb.RemoteBuildpackFetcher, 39 | clusterStack *buildapi.ClusterStack, 40 | clusterLifecycle *buildapi.ClusterLifecycle, 41 | spec buildapi.BuilderSpec, 42 | signingSecrets []*corev1.Secret, 43 | resolvedBuilderTag string, 44 | ) (buildapi.BuilderRecord, error) { 45 | f.CreateBuilderCalls = append(f.CreateBuilderCalls, CreateBuilderArgs{ 46 | Context: ctx, 47 | BuilderKeychain: builderKeychain, 48 | StackKeychain: stackKeychain, 49 | Fetcher: fetcher, 50 | ClusterStack: clusterStack, 51 | ClusterLifecycle: clusterLifecycle, 52 | BuilderSpec: spec, 53 | SigningSecrets: signingSecrets, 54 | ResolvedBuilderTag: resolvedBuilderTag, 55 | }) 56 | 57 | return f.Record, f.CreateErr 58 | } 59 | -------------------------------------------------------------------------------- /pkg/reconciler/testhelpers/fake_tracker.go: -------------------------------------------------------------------------------- 1 | package testhelpers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pivotal/kpack/pkg/reconciler" 7 | "k8s.io/apimachinery/pkg/runtime/schema" 8 | "k8s.io/apimachinery/pkg/types" 9 | ) 10 | 11 | type FakeTracker struct { 12 | objects map[string]map[types.NamespacedName]struct{} 13 | kinds map[string]map[types.NamespacedName]struct{} 14 | } 15 | 16 | func (f *FakeTracker) Track(ref reconciler.Key, obj types.NamespacedName) { 17 | if f.objects == nil { 18 | f.objects = make(map[string]map[types.NamespacedName]struct{}) 19 | } 20 | 21 | _, ok := f.objects[ref.String()] 22 | if !ok { 23 | f.objects[ref.String()] = map[types.NamespacedName]struct{}{} 24 | } 25 | 26 | f.objects[ref.String()][obj] = struct{}{} 27 | } 28 | 29 | func (f *FakeTracker) TrackKind(kind schema.GroupKind, obj types.NamespacedName) { 30 | if f.kinds == nil { 31 | f.kinds = make(map[string]map[types.NamespacedName]struct{}) 32 | } 33 | 34 | _, ok := f.kinds[kind.String()] 35 | if !ok { 36 | f.kinds[kind.String()] = map[types.NamespacedName]struct{}{} 37 | } 38 | 39 | f.kinds[kind.String()][obj] = struct{}{} 40 | } 41 | 42 | func (*FakeTracker) OnChanged(obj interface{}) { 43 | panic("I should not be called in tests") 44 | } 45 | 46 | func (f *FakeTracker) IsTracking(ref reconciler.Key, obj types.NamespacedName) bool { 47 | trackingObs, ok := f.objects[ref.String()] 48 | if !ok { 49 | return false 50 | } 51 | _, ok = trackingObs[obj] 52 | 53 | return ok 54 | } 55 | 56 | func (f *FakeTracker) IsTrackingKind(kind schema.GroupKind, obj types.NamespacedName) bool { 57 | trackingObs, ok := f.kinds[kind.String()] 58 | if !ok { 59 | return false 60 | } 61 | _, ok = trackingObs[obj] 62 | 63 | return ok 64 | } 65 | 66 | func (f FakeTracker) String() string { 67 | return fmt.Sprintf("%#v", f) 68 | } 69 | -------------------------------------------------------------------------------- /pkg/reconciler/testhelpers/json_compactor.go: -------------------------------------------------------------------------------- 1 | package testhelpers 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | func CompactJSON(spacedJSONStr string) string { 10 | var compactedBuffer bytes.Buffer 11 | err := json.Compact(&compactedBuffer, []byte(spacedJSONStr)) 12 | if err != nil { 13 | fmt.Printf("Error compacting JSON string:\n%s\n", spacedJSONStr) 14 | } 15 | return compactedBuffer.String() 16 | } 17 | -------------------------------------------------------------------------------- /pkg/reconciler/testhelpers/reconciler_tester.go: -------------------------------------------------------------------------------- 1 | package testhelpers 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | "github.com/google/go-cmp/cmp/cmpopts" 8 | "k8s.io/apimachinery/pkg/api/resource" 9 | "k8s.io/apimachinery/pkg/runtime" 10 | rtesting "knative.dev/pkg/reconciler/testing" 11 | ) 12 | 13 | func ReconcilerTester(t *testing.T, factory rtesting.Factory) SpecReconcilerTester { 14 | return SpecReconcilerTester{ 15 | t: t, 16 | factory: factory, 17 | } 18 | } 19 | 20 | type SpecReconcilerTester struct { 21 | t *testing.T 22 | factory rtesting.Factory 23 | } 24 | 25 | func (rt SpecReconcilerTester) Test(test rtesting.TableRow) { 26 | rt.t.Helper() 27 | originObjects := []runtime.Object{} 28 | for _, obj := range test.Objects { 29 | originObjects = append(originObjects, obj.DeepCopyObject()) 30 | } 31 | 32 | test.Test(rt.t, rt.factory) 33 | 34 | // Validate cached objects do not get soiled after controller loops 35 | if diff := cmp.Diff(originObjects, test.Objects, safeDeployDiff, cmpopts.EquateEmpty()); diff != "" { 36 | rt.t.Errorf("Unexpected objects in test %s (-want, +got): %v", test.Name, diff) 37 | } 38 | } 39 | 40 | var ( 41 | safeDeployDiff = cmpopts.IgnoreUnexported(resource.Quantity{}) 42 | ) 43 | -------------------------------------------------------------------------------- /pkg/reconciler/tracker.go: -------------------------------------------------------------------------------- 1 | package reconciler 2 | 3 | import ( 4 | "fmt" 5 | 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | "k8s.io/apimachinery/pkg/types" 8 | ) 9 | 10 | type Key struct { 11 | GroupKind schema.GroupKind 12 | NamespacedName types.NamespacedName 13 | } 14 | 15 | func (k Key) String() string { 16 | return fmt.Sprintf("%s/%s", k.GroupKind, k.NamespacedName) 17 | } 18 | 19 | func (k Key) WithNamespace(namespace string) Key { 20 | return Key{ 21 | GroupKind: k.GroupKind, 22 | NamespacedName: types.NamespacedName{ 23 | Namespace: namespace, 24 | Name: k.NamespacedName.Name, 25 | }, 26 | } 27 | } 28 | 29 | type Object interface { 30 | GetName() string 31 | GetNamespace() string 32 | GetObjectKind() schema.ObjectKind 33 | } 34 | 35 | func KeyForObject(obj Object) Key { 36 | return Key{ 37 | NamespacedName: types.NamespacedName{ 38 | Name: obj.GetName(), 39 | Namespace: obj.GetNamespace(), 40 | }, 41 | GroupKind: obj.GetObjectKind().GroupVersionKind().GroupKind(), 42 | } 43 | } 44 | 45 | type Tracker interface { 46 | Track(ref Key, obj types.NamespacedName) 47 | TrackKind(kind schema.GroupKind, obj types.NamespacedName) 48 | OnChanged(obj interface{}) 49 | } 50 | -------------------------------------------------------------------------------- /pkg/registry/keychain_factory.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/google/go-containerregistry/pkg/authn" 7 | v1 "k8s.io/api/core/v1" 8 | ) 9 | 10 | type SecretRef struct { 11 | ServiceAccount string 12 | Namespace string 13 | ImagePullSecrets []v1.LocalObjectReference 14 | } 15 | 16 | func (s SecretRef) IsNamespaced() bool { 17 | return s.Namespace != "" 18 | } 19 | 20 | func (s SecretRef) ServiceAccountOrDefault() string { 21 | if s.ServiceAccount == "" { 22 | return "default" 23 | } 24 | return s.ServiceAccount 25 | } 26 | 27 | type KeychainFactory interface { 28 | KeychainForSecretRef(context.Context, SecretRef) (authn.Keychain, error) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/registry/registryfakes/fake_keychain_factory.go: -------------------------------------------------------------------------------- 1 | package registryfakes 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/google/go-containerregistry/pkg/authn" 8 | "github.com/pkg/errors" 9 | "k8s.io/apimachinery/pkg/api/equality" 10 | 11 | "github.com/pivotal/kpack/pkg/registry" 12 | ) 13 | 14 | type keychainContainer struct { 15 | SecretRef registry.SecretRef 16 | Keychain authn.Keychain 17 | } 18 | 19 | type FakeKeychainFactory struct { 20 | keychains []keychainContainer 21 | } 22 | 23 | func (f *FakeKeychainFactory) KeychainForSecretRef(ctx context.Context, secretRef registry.SecretRef) (authn.Keychain, error) { 24 | if keychain, ok := f.getKeychainForSecretRef(secretRef); ok { 25 | return keychain, nil 26 | } 27 | return nil, errors.Errorf("unable to find keychain for secret ref: %+v", secretRef) 28 | } 29 | 30 | func (f *FakeKeychainFactory) AddKeychainForSecretRef(t *testing.T, secretRef registry.SecretRef, keychain authn.Keychain) { 31 | t.Helper() 32 | 33 | if _, ok := f.getKeychainForSecretRef(secretRef); ok { 34 | t.Errorf("secret ref '%+v' already has a keychain", secretRef) 35 | return 36 | } 37 | 38 | f.keychains = append(f.keychains, keychainContainer{ 39 | SecretRef: secretRef, 40 | Keychain: keychain, 41 | }) 42 | } 43 | 44 | func (f *FakeKeychainFactory) getKeychainForSecretRef(secretRef registry.SecretRef) (authn.Keychain, bool) { 45 | for _, item := range f.keychains { 46 | if equality.Semantic.DeepEqual(item.SecretRef, secretRef) { 47 | return item.Keychain, true 48 | } 49 | } 50 | return nil, false 51 | } 52 | 53 | type FakeKeychain struct { 54 | Name string 55 | } 56 | 57 | func (f *FakeKeychain) Resolve(authn.Resource) (authn.Authenticator, error) { 58 | return authn.Anonymous, nil 59 | } 60 | -------------------------------------------------------------------------------- /pkg/registry/resolver.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "context" 5 | 6 | buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" 7 | corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" 8 | ) 9 | 10 | type Resolver struct { 11 | } 12 | 13 | func (*Resolver) Resolve(ctx context.Context, sourceResolver *buildapi.SourceResolver) (corev1alpha1.ResolvedSourceConfig, error) { 14 | return corev1alpha1.ResolvedSourceConfig{ 15 | Registry: &corev1alpha1.ResolvedRegistrySource{ 16 | Image: sourceResolver.Spec.Source.Registry.Image, 17 | ImagePullSecrets: sourceResolver.Spec.Source.Registry.ImagePullSecrets, 18 | SubPath: sourceResolver.Spec.Source.SubPath, 19 | }, 20 | }, nil 21 | } 22 | 23 | func (*Resolver) CanResolve(sourceResolver *buildapi.SourceResolver) bool { 24 | return sourceResolver.IsRegistry() 25 | } 26 | -------------------------------------------------------------------------------- /pkg/registry/testdata/layer.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/pkg/registry/testdata/layer.tar -------------------------------------------------------------------------------- /pkg/registry/testdata/reg.tar: -------------------------------------------------------------------------------- 1 | test.txt000644 000765 000024 00000000023 13733167452 013425 0ustar00pivotalstaff000000 000000 test file contents 2 | -------------------------------------------------------------------------------- /pkg/registry/testdata/targz.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/pkg/registry/testdata/targz.tar -------------------------------------------------------------------------------- /pkg/registry/testdata/zip.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildpacks-community/kpack/137091cb2b3feccd608e3132caddfe71ac94e899/pkg/registry/testdata/zip.tar -------------------------------------------------------------------------------- /pkg/secret/constants.go: -------------------------------------------------------------------------------- 1 | package secret 2 | 3 | const ( 4 | CosignSecretPrivateKey = "cosign.key" 5 | CosignSecretPassword = "cosign.password" 6 | CosignSecretPublicKey = "cosign.pub" 7 | 8 | CosignDockerMediaTypesAnnotation = "kpack.io/cosign.docker-media-types" 9 | CosignRepositoryAnnotation = "kpack.io/cosign.repository" 10 | 11 | PKCS8SecretKey = "ssh-privatekey" 12 | 13 | SLSASecretAnnotation = "kpack.io/slsa" 14 | SLSADockerMediaTypesAnnotation = "kpack.io/slsa.docker-media-types" 15 | ) 16 | -------------------------------------------------------------------------------- /pkg/secret/fetcher.go: -------------------------------------------------------------------------------- 1 | package secret 2 | 3 | import ( 4 | "context" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | k8serrors "k8s.io/apimachinery/pkg/api/errors" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | k8sclient "k8s.io/client-go/kubernetes" 10 | ) 11 | 12 | type Fetcher struct { 13 | Client k8sclient.Interface 14 | 15 | SystemNamespace string 16 | SystemServiceAccountName string 17 | } 18 | 19 | func (f *Fetcher) SecretsForServiceAccount(ctx context.Context, serviceAccount, namespace string) ([]*corev1.Secret, error) { 20 | sa, err := f.Client.CoreV1().ServiceAccounts(namespace).Get(ctx, serviceAccount, metav1.GetOptions{}) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return f.secretsFromServiceAccount(ctx, sa, namespace) 25 | } 26 | 27 | func (f *Fetcher) secretsFromServiceAccount(ctx context.Context, account *corev1.ServiceAccount, namespace string) ([]*corev1.Secret, error) { 28 | var secrets []*corev1.Secret 29 | for _, secretRef := range account.Secrets { 30 | secret, err := f.Client.CoreV1().Secrets(namespace).Get(ctx, secretRef.Name, metav1.GetOptions{}) 31 | if err != nil && !k8serrors.IsNotFound(err) { 32 | return nil, err 33 | } else if k8serrors.IsNotFound(err) { 34 | continue 35 | } 36 | secrets = append(secrets, secret) 37 | } 38 | return secrets, nil 39 | } 40 | 41 | func (f *Fetcher) SecretsForSystemServiceAccount(ctx context.Context) ([]*corev1.Secret, error) { 42 | return f.SecretsForServiceAccount(ctx, f.SystemServiceAccountName, f.SystemNamespace) 43 | } 44 | -------------------------------------------------------------------------------- /pkg/secret/secret_types.go: -------------------------------------------------------------------------------- 1 | package secret 2 | 3 | const ( 4 | SSHAuthKnownHostsKey = "known_hosts" 5 | ) 6 | 7 | type BasicAuth struct { 8 | Username string 9 | Password string 10 | } 11 | 12 | type SSH struct { 13 | PrivateKey string 14 | KnownHosts string 15 | } 16 | -------------------------------------------------------------------------------- /pkg/secret/secretfakes/fake_fetcher.go: -------------------------------------------------------------------------------- 1 | package secretfakes 2 | 3 | import ( 4 | "context" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | ) 8 | 9 | type FakeFetchSecret struct { 10 | FakeSecrets []*corev1.Secret 11 | ShouldError bool 12 | ErrorOut error 13 | 14 | SecretsForServiceAccountFunc func(context.Context, string, string) ([]*corev1.Secret, error) 15 | } 16 | 17 | func (f *FakeFetchSecret) SecretsForServiceAccount(ctx context.Context, serviceAccount, namespace string) ([]*corev1.Secret, error) { 18 | if f.SecretsForServiceAccountFunc != nil { 19 | return f.SecretsForServiceAccount(ctx, serviceAccount, namespace) 20 | } 21 | 22 | if f.ShouldError { 23 | return nil, f.ErrorOut 24 | } 25 | 26 | return f.FakeSecrets, nil 27 | } 28 | -------------------------------------------------------------------------------- /pkg/secret/volume_secret_reader.go: -------------------------------------------------------------------------------- 1 | package secret 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | corev1 "k8s.io/api/core/v1" 9 | ) 10 | 11 | func ReadBasicAuthSecret(secretVolume, secretName string) (BasicAuth, error) { 12 | secretPath := volumeName(secretVolume, secretName) 13 | ub, err := os.ReadFile(filepath.Join(secretPath, corev1.BasicAuthUsernameKey)) 14 | if err != nil { 15 | return BasicAuth{}, err 16 | } 17 | username := string(ub) 18 | 19 | pb, err := os.ReadFile(filepath.Join(secretPath, corev1.BasicAuthPasswordKey)) 20 | if err != nil { 21 | return BasicAuth{}, err 22 | } 23 | password := string(pb) 24 | 25 | return BasicAuth{ 26 | Username: username, 27 | Password: password, 28 | }, nil 29 | } 30 | 31 | func ReadSshSecret(secretVolume, secretName string) (SSH, error) { 32 | secretPath := volumeName(secretVolume, secretName) 33 | privateKey, err := os.ReadFile(filepath.Join(secretPath, corev1.SSHAuthPrivateKey)) 34 | if err != nil { 35 | return SSH{}, err 36 | } 37 | 38 | var knownHosts []byte = nil 39 | knownHostsPath := filepath.Join(secretPath, SSHAuthKnownHostsKey) 40 | if _, err := os.Stat(knownHostsPath); !os.IsNotExist(err) { 41 | knownHosts, err = os.ReadFile(knownHostsPath) 42 | if err != nil { 43 | return SSH{}, err 44 | } 45 | } 46 | 47 | return SSH{ 48 | PrivateKey: string(privateKey), 49 | KnownHosts: string(knownHosts), 50 | }, nil 51 | } 52 | 53 | func volumeName(VolumePath, secretName string) string { 54 | return fmt.Sprintf("%s/%s", VolumePath, secretName) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/slsa/cosign_signer.go: -------------------------------------------------------------------------------- 1 | package slsa 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | 7 | "github.com/secure-systems-lab/go-securesystemslib/dsse" 8 | "github.com/sigstore/cosign/v2/pkg/cosign" 9 | "github.com/sigstore/sigstore/pkg/signature" 10 | ) 11 | 12 | var _ dsse.Signer = (*cosignSigner)(nil) 13 | 14 | type cosignSigner struct { 15 | signer signature.Signer 16 | keyid string 17 | } 18 | 19 | // NewCosignSigner loads a cosign private key into a dsse signer. The main difference between this signer and the one 20 | // provided by sigstore's dsse.WrappedSigner is that this signer doesn't compute the PAE when signing 21 | func NewCosignSigner(key, pass []byte, id string) (*cosignSigner, error) { 22 | sv, err := cosign.LoadPrivateKey(key, pass) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | return &cosignSigner{ 28 | signer: sv, 29 | keyid: id, 30 | }, nil 31 | } 32 | 33 | // KeyID implements dsse.Signer. 34 | func (s *cosignSigner) KeyID() (string, error) { 35 | return s.keyid, nil 36 | } 37 | 38 | // Sign implements dsse.Signer. 39 | func (s *cosignSigner) Sign(ctx context.Context, data []byte) ([]byte, error) { 40 | return s.signer.SignMessage(bytes.NewReader(data)) 41 | } 42 | -------------------------------------------------------------------------------- /rfcs/0000-template.md: -------------------------------------------------------------------------------- 1 | **How to Submit:** 2 | * Copy 0000-template.md to rfcs/0000-my-feature.md (where 'my-feature' is descriptive. don't assign an RFC number yet). 3 | * Fill in RFC. Any section can be marked as "N/A" if not applicable. 4 | * Submit a pull request. The PR is a place for conversation and discussion. 5 | * Include the following line in the PR description: 6 | 7 | `[Readable](https://github.com/your-name-or-org/kpack/blob//rfcs/0000-.md)` 8 | * Once the team approves it, the PR can be merged. 9 | 10 | **Problem:** 11 | * What is the current problem for the users/ourselves? 12 | 13 | **Outcome:** 14 | * What do we want to achieve with this? 15 | 16 | **Actions to take:** 17 | * What actions should we take? 18 | 19 | **Complexity:** 20 | * What thoughts do you have on the complexity of this? 21 | 22 | **Prior Art:** 23 | * Any issues? Previous Ideas? Other Projects? 24 | 25 | **Alternatives:** 26 | * What could we do instead? 27 | 28 | **Risks:** 29 | * What makes this proposal risky? -------------------------------------------------------------------------------- /rfcs/0012-remove-windows-support.md: -------------------------------------------------------------------------------- 1 | **Problem:** 2 | The upstream buildpacks has [removed support for windows](https://github.com/buildpacks/rfcs/blob/main/text/0133-remove-windows-containers-support.md), which will lead to the removal of a windows lifecycle. Without this, kpack cannot run windows builds. 3 | 4 | **Outcome:** 5 | Windows support will be removed from kpack. This feature is not used and [discussions to remove it](https://github.com/buildpacks-community/kpack/discussions/1366) were ongoing before the removal of Windows support in the lifecycle. 6 | 7 | **Actions to take:** 8 | Remove all windows support from kpack. Error if an existing Windows builder is used during a build. 9 | 10 | **Complexity:** 11 | Low Complexity 12 | 13 | **Risks:** 14 | Due to the fact that there are no known users of Windows support in kpack, there is very little risk with this proposal 15 | -------------------------------------------------------------------------------- /rfcs/README.md: -------------------------------------------------------------------------------- 1 | ## kpack rfcs 2 | 3 | Want to suggest a change to the kpack project? Awesome! 4 | 5 | We follow an RFC (Request for Comments) process for substantial changes to the project. -------------------------------------------------------------------------------- /samples/build.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kpack.io/v1alpha2 2 | kind: Build 3 | metadata: 4 | name: sample 5 | spec: 6 | tags: 7 | - sample/image 8 | serviceAccountName: serviceaccount 9 | builder: 10 | image: gcr.io/paketo-buildpacks/builder:base 11 | source: 12 | git: 13 | url: https://github.com/buildpack/sample-java-app.git 14 | revision: main 15 | -------------------------------------------------------------------------------- /samples/builder.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kpack.io/v1alpha2 2 | kind: Builder 3 | metadata: 4 | name: my-builder 5 | namespace: default 6 | spec: 7 | serviceAccountName: default 8 | tag: sample/builder 9 | stack: 10 | name: base 11 | kind: ClusterStack 12 | store: 13 | name: default 14 | kind: ClusterStore 15 | order: 16 | - group: 17 | - id: paketo-buildpacks/java 18 | - group: 19 | - id: paketo-buildpacks/nodejs 20 | -------------------------------------------------------------------------------- /samples/cluster_builder.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kpack.io/v1alpha2 2 | kind: ClusterBuilder 3 | metadata: 4 | name: my-cluster-builder 5 | spec: 6 | tag: buildingbuilder/cluster-builder 7 | stack: 8 | name: bionic-stack 9 | kind: ClusterStack 10 | store: 11 | name: sample-cluster-store 12 | kind: ClusterStore 13 | serviceAccountRef: 14 | name: default 15 | namespace: default 16 | order: 17 | - group: 18 | - id: paketo-buildpacks/java 19 | - group: 20 | - id: paketo-buildpacks/nodejs 21 | -------------------------------------------------------------------------------- /samples/cluster_stack.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kpack.io/v1alpha2 2 | kind: ClusterStack 3 | metadata: 4 | name: default 5 | spec: 6 | id: "io.buildpacks.stacks.bionic" 7 | serviceAccountRef: 8 | name: default 9 | namespace: default 10 | buildImage: 11 | image: "paketobuildpacks/build:base-cnb" 12 | runImage: 13 | image: "paketobuildpacks/run:base-cnb" 14 | -------------------------------------------------------------------------------- /samples/cluster_store.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kpack.io/v1alpha2 2 | kind: ClusterStore 3 | metadata: 4 | name: sample-cluster-store 5 | spec: 6 | serviceAccountRef: 7 | name: default 8 | namespace: default 9 | sources: 10 | - image: gcr.io/paketo-buildpacks/java 11 | - image: gcr.io/paketo-buildpacks/nodejs 12 | -------------------------------------------------------------------------------- /samples/image_from_blob_url.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kpack.io/v1alpha2 2 | kind: Image 3 | metadata: 4 | name: sample 5 | spec: 6 | tag: sample/image-from-jar 7 | builder: 8 | kind: ClusterBuilder 9 | name: cluster-sample-builder 10 | serviceAccountName: service-account 11 | source: 12 | blob: 13 | url: https://storage.googleapis.com/build-service/sample-apps/spring-petclinic-2.1.0.BUILD-SNAPSHOT.jar 14 | -------------------------------------------------------------------------------- /samples/image_from_git.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kpack.io/v1alpha2 2 | kind: Image 3 | metadata: 4 | name: sample-1 5 | spec: 6 | tag: sample/image-from-git 7 | builder: 8 | kind: ClusterBuilder 9 | name: cluster-sample-builder 10 | serviceAccountName: service-account 11 | source: 12 | git: 13 | url: https://github.com/buildpack/sample-java-app.git 14 | revision: main 15 | --- 16 | apiVersion: kpack.io/v1alpha2 17 | kind: Image 18 | metadata: 19 | name: sample-2 20 | spec: 21 | tag: sample/image-from-git-specific-sha 22 | builder: 23 | kind: Builder 24 | name: sample-builder 25 | serviceAccountName: service-account 26 | source: 27 | git: 28 | url: https://github.com/buildpack/sample-java-app.git 29 | revision: 0eccc6c2f01d9f055087ebbf03526ed0623e014a 30 | --- 31 | apiVersion: kpack.io/v1alpha2 32 | kind: Image 33 | metadata: 34 | name: sample-3 35 | spec: 36 | tag: sample/image-from-git-tag 37 | builder: 38 | kind: ClusterBuilder 39 | name: cluster-sample-builder 40 | serviceAccountName: service-account 41 | source: 42 | git: 43 | url: https://github.com/buildpack/sample-java-app.git 44 | revision: sample-0.0.1 45 | -------------------------------------------------------------------------------- /samples/image_with_cache.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kpack.io/v1alpha2 2 | kind: Image 3 | metadata: 4 | name: sample-with-volume-cache 5 | spec: 6 | tag: sample/image-from-git 7 | builder: 8 | kind: ClusterBuilder 9 | name: cluster-sample-builder 10 | serviceAccountName: service-account 11 | cache: 12 | volume: 13 | request: 1G 14 | source: 15 | git: 16 | url: https://github.com/buildpack/sample-java-app.git 17 | revision: master 18 | --- 19 | apiVersion: kpack.io/v1alpha2 20 | kind: Image 21 | metadata: 22 | name: sample-with-volume-cache 23 | spec: 24 | tag: sample/image-from-git 25 | builder: 26 | kind: ClusterBuilder 27 | name: cluster-sample-builder 28 | serviceAccountName: service-account 29 | cache: 30 | registry: 31 | tag: sample/image-from-git-cache 32 | source: 33 | git: 34 | url: https://github.com/buildpack/sample-java-app.git 35 | revision: master 36 | -------------------------------------------------------------------------------- /samples/image_with_legacy_cnb_service_bindings.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kpack.io/v1alpha1 2 | kind: Image 3 | metadata: 4 | name: sample-binding 5 | spec: 6 | tag: sample/image-with-binding 7 | builder: 8 | kind: Builder 9 | name: sample-builder 10 | serviceAccount: service-account 11 | source: 12 | git: 13 | url: https://github.com/buildpack/sample-java-app.git 14 | revision: 0eccc6c2f01d9f055087ebbf03526ed0623e014a 15 | build: 16 | bindings: 17 | - name: sample 18 | metadataRef: 19 | name: sample-binding-metadata 20 | --- 21 | apiVersion: kpack.io/v1alpha1 22 | kind: Image 23 | metadata: 24 | name: sample-binding-with-secret 25 | spec: 26 | tag: sample/image-with-binding-secret 27 | builder: 28 | kind: Builder 29 | name: sample-builder 30 | serviceAccount: service-account 31 | source: 32 | git: 33 | url: https://github.com/buildpack/sample-java-app.git 34 | revision: 0eccc6c2f01d9f055087ebbf03526ed0623e014a 35 | build: 36 | bindings: 37 | - name: sample 38 | metadataRef: 39 | name: sample-binding-metadata 40 | secretRef: 41 | name: sample-binding-secret 42 | --- 43 | apiVersion: v1 44 | kind: ConfigMap 45 | metadata: 46 | name: sample-binding-metadata 47 | data: 48 | kind: mysql 49 | provider: sample 50 | tags: "" 51 | --- 52 | apiVersion: v1 53 | kind: Secret 54 | metadata: 55 | name: sample-binding-secret 56 | type: Opaque 57 | stringData: 58 | hostname: localhost 59 | jdbcUrl: jdbc:mysql://localhost:3306/default?user=root&password= 60 | name: default 61 | password: "" 62 | port: "3306" 63 | uri: mysql://root:@localhost:3306/default?reconnect=true 64 | username: root 65 | -------------------------------------------------------------------------------- /samples/image_with_service_bindings.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kpack.io/v1alpha2 2 | kind: Image 3 | metadata: 4 | name: sample-binding 5 | spec: 6 | tag: sample/image-with-binding 7 | builder: 8 | kind: Builder 9 | name: sample-builder 10 | serviceAccountName: service-account 11 | source: 12 | git: 13 | url: https://github.com/buildpack/sample-java-app.git 14 | revision: 0eccc6c2f01d9f055087ebbf03526ed0623e014a 15 | build: 16 | services: 17 | - name: sample-binding-secret 18 | kind: Secret 19 | apiVersion: v1 20 | --- 21 | apiVersion: v1 22 | kind: Secret 23 | metadata: 24 | name: sample-binding-secret 25 | type: Opaque 26 | stringData: 27 | type: mysql 28 | provider: sample 29 | hostname: localhost 30 | jdbcUrl: jdbc:mysql://localhost:3306/default?user=root&password= 31 | name: default 32 | password: "" 33 | port: "3306" 34 | uri: mysql://root:@localhost:3306/default?reconnect=true 35 | username: root 36 | -------------------------------------------------------------------------------- /test/e2e.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "os/user" 7 | "path" 8 | "sync" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | "k8s.io/client-go/dynamic" 13 | "k8s.io/client-go/kubernetes" 14 | "k8s.io/client-go/rest" 15 | "k8s.io/client-go/tools/clientcmd" 16 | 17 | "github.com/pivotal/kpack/pkg/client/clientset/versioned" 18 | ) 19 | 20 | var ( 21 | setup sync.Once 22 | client *versioned.Clientset 23 | k8sClient *kubernetes.Clientset 24 | dynamicClient dynamic.Interface 25 | clusterConfig *rest.Config 26 | ) 27 | 28 | func newClients(t *testing.T) (*clients, error) { 29 | setup.Do(func() { 30 | var err error 31 | kubeconfig := flag.String("kubeconfig", getKubeConfig(), "Path to a kubeconfig. Only required if out-of-cluster.") 32 | masterURL := flag.String("master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.") 33 | 34 | flag.Parse() 35 | 36 | clusterConfig, err = clientcmd.BuildConfigFromFlags(*masterURL, *kubeconfig) 37 | require.NoError(t, err) 38 | 39 | client, err = versioned.NewForConfig(clusterConfig) 40 | require.NoError(t, err) 41 | 42 | k8sClient, err = kubernetes.NewForConfig(clusterConfig) 43 | require.NoError(t, err) 44 | 45 | dynamicClient, err = dynamic.NewForConfig(clusterConfig) 46 | require.NoError(t, err) 47 | }) 48 | 49 | return &clients{ 50 | client: client, 51 | k8sClient: k8sClient, 52 | dynamicClient: dynamicClient, 53 | }, nil 54 | } 55 | 56 | func getKubeConfig() string { 57 | if config, found := os.LookupEnv("KUBECONFIG"); found { 58 | return config 59 | } 60 | if usr, err := user.Current(); err == nil { 61 | return path.Join(usr.HomeDir, ".kube/config") 62 | } 63 | return "" 64 | } 65 | 66 | type clients struct { 67 | client versioned.Interface 68 | k8sClient kubernetes.Interface 69 | dynamicClient dynamic.Interface 70 | } 71 | --------------------------------------------------------------------------------