├── .gcloudignore ├── VERSION ├── .rgignore ├── test-e2e ├── golden-images │ ├── content │ │ ├── bar │ │ │ └── 1.0 │ │ └── foo │ │ │ ├── NOTAG-0 │ │ │ ├── 1.0-linux_amd64 │ │ │ └── 1.0-linux_s390x │ └── push-golden.sh ├── cip-auditor │ ├── fixture │ │ ├── basic │ │ │ ├── images │ │ │ │ ├── bar │ │ │ │ │ └── images.yaml │ │ │ │ └── foo │ │ │ │ │ └── images.yaml │ │ │ └── manifests │ │ │ │ ├── bar │ │ │ │ └── promoter-manifest.yaml │ │ │ │ └── foo │ │ │ │ └── promoter-manifest.yaml │ │ ├── fatManifest │ │ │ ├── images │ │ │ │ └── foo │ │ │ │ │ └── images.yaml │ │ │ └── manifests │ │ │ │ └── foo │ │ │ │ └── promoter-manifest.yaml │ │ └── fatManifest-subproject-different-prefix │ │ │ ├── images │ │ │ └── foo │ │ │ │ └── images.yaml │ │ │ └── manifests │ │ │ └── foo │ │ │ └── promoter-manifest.yaml │ └── entrypoint-from-container.sh ├── cip │ ├── fixture │ │ ├── recursive-thin │ │ │ ├── images │ │ │ │ ├── bar │ │ │ │ │ └── images.yaml │ │ │ │ └── foo │ │ │ │ │ └── images.yaml │ │ │ └── manifests │ │ │ │ ├── bar │ │ │ │ └── promoter-manifest.yaml │ │ │ │ └── foo │ │ │ │ └── promoter-manifest.yaml │ │ └── sanity │ │ │ └── promoter-manifest.yaml │ ├── e2e-entrypoint-from-container.sh │ └── tests.yaml └── README.md ├── image ├── manifest │ └── testdata │ │ ├── empty │ │ └── non-manifest.txt │ │ └── singleton │ │ ├── images │ │ └── a │ │ │ └── images.yaml │ │ └── manifests │ │ └── a │ │ └── promoter-manifest.yaml └── consts │ └── consts.go ├── internal ├── legacy │ ├── dockerregistry │ │ ├── inventory_test │ │ │ ├── TestParseThinManifestsFromDir │ │ │ │ ├── empty │ │ │ │ │ └── non-manifest.txt │ │ │ │ ├── basic-thin │ │ │ │ │ ├── images │ │ │ │ │ │ ├── a │ │ │ │ │ │ │ └── images.yaml │ │ │ │ │ │ ├── b │ │ │ │ │ │ │ └── images.yaml │ │ │ │ │ │ ├── c │ │ │ │ │ │ │ └── images.yaml │ │ │ │ │ │ └── d │ │ │ │ │ │ │ └── images.yaml │ │ │ │ │ └── manifests │ │ │ │ │ │ ├── d │ │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ │ │ ├── a │ │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ │ │ ├── b │ │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ │ │ └── c │ │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ ├── singleton │ │ │ │ │ ├── images │ │ │ │ │ │ └── a │ │ │ │ │ │ │ └── images.yaml │ │ │ │ │ └── manifests │ │ │ │ │ │ └── a │ │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ └── multiple-rebases │ │ │ │ │ ├── images │ │ │ │ │ ├── a │ │ │ │ │ │ └── images.yaml │ │ │ │ │ └── b │ │ │ │ │ │ └── images.yaml │ │ │ │ │ └── manifests │ │ │ │ │ ├── a │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ │ └── b │ │ │ │ │ └── promoter-manifest.yaml │ │ │ └── TestValidateThinManifestsFromDir │ │ │ │ ├── invalid │ │ │ │ ├── empty │ │ │ │ │ └── non-manifest.txt │ │ │ │ ├── malformed-directory-tree-structure │ │ │ │ │ ├── images │ │ │ │ │ │ └── a │ │ │ │ │ │ │ └── images.yaml │ │ │ │ │ └── manifests │ │ │ │ │ │ ├── a │ │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ │ │ └── b │ │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ ├── malformed-directory-tree-structure-nested │ │ │ │ │ ├── images │ │ │ │ │ │ └── a │ │ │ │ │ │ │ └── images.yaml │ │ │ │ │ ├── manifests │ │ │ │ │ │ ├── images │ │ │ │ │ │ │ └── c │ │ │ │ │ │ │ │ └── images.yaml │ │ │ │ │ │ ├── a │ │ │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ │ │ └── b │ │ │ │ │ │ │ └── c │ │ │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ │ └── README.md │ │ │ │ └── overlapping-destination-vertices-different-digest │ │ │ │ │ ├── images │ │ │ │ │ ├── a │ │ │ │ │ │ └── images.yaml │ │ │ │ │ └── b │ │ │ │ │ │ └── images.yaml │ │ │ │ │ └── manifests │ │ │ │ │ ├── a │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ │ └── b │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ └── valid │ │ │ │ ├── singleton │ │ │ │ ├── images │ │ │ │ │ └── a │ │ │ │ │ │ └── images.yaml │ │ │ │ └── manifests │ │ │ │ │ └── a │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ ├── multiple-rebases │ │ │ │ ├── images │ │ │ │ │ ├── a │ │ │ │ │ │ └── images.yaml │ │ │ │ │ └── b │ │ │ │ │ │ └── images.yaml │ │ │ │ └── manifests │ │ │ │ │ ├── a │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ │ └── b │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ ├── overlapping-src-registries │ │ │ │ ├── images │ │ │ │ │ ├── a │ │ │ │ │ │ └── images.yaml │ │ │ │ │ └── b │ │ │ │ │ │ └── images.yaml │ │ │ │ └── manifests │ │ │ │ │ ├── a │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ │ └── b │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ ├── overlapping-destination-vertices-same-digest │ │ │ │ ├── images │ │ │ │ │ ├── a │ │ │ │ │ │ └── images.yaml │ │ │ │ │ └── b │ │ │ │ │ │ └── images.yaml │ │ │ │ └── manifests │ │ │ │ │ ├── a │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ │ └── b │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ └── malformed-directory-tree-structure-bad-prefix-is-ignored │ │ │ │ ├── images │ │ │ │ └── a │ │ │ │ │ └── images.yaml │ │ │ │ ├── README.md │ │ │ │ ├── manifest │ │ │ │ └── b │ │ │ │ │ └── promoter-manifest.yaml │ │ │ │ └── manifests │ │ │ │ └── a │ │ │ │ └── promoter-manifest.yaml │ │ ├── registry │ │ │ ├── context.go │ │ │ └── set.go │ │ └── schema │ │ │ └── manifest_test.go │ ├── cli │ │ ├── root.go │ │ ├── run.go │ │ └── audit.go │ ├── remotemanifest │ │ ├── types.go │ │ └── fake.go │ ├── logclient │ │ ├── types.go │ │ ├── fake.go │ │ └── gcp.go │ ├── json │ │ └── json.go │ ├── report │ │ ├── gcp.go │ │ ├── types.go │ │ └── fake.go │ ├── stream │ │ ├── fake.go │ │ ├── subprocess.go │ │ ├── types.go │ │ └── http.go │ ├── timewrapper │ │ └── timewrapper.go │ ├── container │ │ └── set.go │ ├── audit │ │ └── types.go │ ├── signals │ │ └── signals.go │ └── gcloud │ │ └── token.go ├── tools.go ├── version │ ├── version_test.go │ └── version.go └── promoter │ └── image │ ├── securityscan.go │ └── sign_test.go ├── promobot ├── testdata │ ├── files │ │ ├── blue.png │ │ ├── red.png │ │ └── green.png │ ├── manifests │ │ ├── onefile │ │ │ ├── filepromoter-manifest.yaml │ │ │ └── files.yaml │ │ ├── manyfiles │ │ │ ├── filepromoter-manifest.yaml │ │ │ └── files │ │ │ │ ├── red.yaml │ │ │ │ ├── blue.yaml │ │ │ │ └── green.yaml │ │ └── manyprojects │ │ │ ├── manifests │ │ │ ├── project1 │ │ │ │ ├── red.yaml │ │ │ │ ├── blue.yaml │ │ │ │ └── green.yaml │ │ │ └── project2 │ │ │ │ ├── red.yaml │ │ │ │ ├── blue.yaml │ │ │ │ └── green.yaml │ │ │ └── filestores │ │ │ ├── project1 │ │ │ └── filepromoter-manifest.yaml │ │ │ └── project2 │ │ │ └── filepromoter-manifest.yaml │ ├── files-manifest.yaml │ └── expected │ │ ├── manyfiles.yaml │ │ ├── onefile.yaml │ │ └── manyprojects.yaml ├── readmanifest_test.go ├── hash_test.go └── hash.go ├── canary ├── images │ ├── sigstore │ │ └── images.yaml │ └── k8s-staging-bom │ │ └── images.yaml └── manifests │ ├── k8s-staging-bom │ └── promoter-manifest.yaml │ └── sigstore │ └── promoter-manifest.yaml ├── code-of-conduct.md ├── container-structure.yaml ├── .gitignore ├── cmd ├── cip-mm │ └── README.md ├── kpromo │ ├── main.go │ └── cmd │ │ ├── run │ │ ├── run.go │ │ └── files.go │ │ ├── manifest │ │ ├── manifest.go │ │ └── files.go │ │ ├── cip │ │ └── audit.go │ │ ├── mm │ │ └── mm.go │ │ └── root.go └── verify-gcr-quota │ └── main.go ├── OWNERS ├── .github ├── ISSUE_TEMPLATE │ ├── feature.md │ ├── bug-report.md │ └── cut-release.md ├── dependabot.yml ├── SECURITY.md └── PULL_REQUEST_TEMPLATE.md ├── hack ├── boilerplate │ ├── boilerplate.go.txt │ ├── boilerplate.generatego.txt │ ├── boilerplate.py.txt │ ├── boilerplate.sh.txt │ ├── boilerplate.Dockerfile.txt │ └── boilerplate.Makefile.txt ├── verify-build.sh ├── verify-go-mod.sh ├── verify-mocks.sh ├── verify-boilerplate.sh ├── verify-dependencies.sh ├── local-audit.sh ├── verify-golangci-lint.sh ├── test-go.sh ├── verify-archives.sh └── init-buildx.sh ├── SECURITY_CONTACTS ├── CONTRIBUTING.md ├── promoter ├── image │ ├── checkresults │ │ └── checkresults.go │ └── ratelimit │ │ └── roundtripper.go └── file │ ├── interfaces.go │ ├── token.go │ ├── manifest.go │ ├── filestore_test.go │ ├── file.go │ └── filefakes │ └── fake_sync_file_op.go ├── OWNERS_ALIASES ├── types └── image │ └── image.go ├── cloudbuild.yaml ├── go_with_version.sh ├── docker └── config.json ├── Dockerfile ├── dependencies.yaml ├── workspace_status.sh ├── api └── files │ ├── manifest.go │ └── validation.go ├── gh2gcs └── gh2gcs.go └── docs └── file-promotion.md /.gcloudignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 4.1.0 2 | -------------------------------------------------------------------------------- /.rgignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /test-e2e/golden-images/content/bar/1.0: -------------------------------------------------------------------------------- 1 | bar 1.0 2 | -------------------------------------------------------------------------------- /test-e2e/golden-images/content/foo/NOTAG-0: -------------------------------------------------------------------------------- 1 | foo NOTAG-0 2 | -------------------------------------------------------------------------------- /image/manifest/testdata/empty/non-manifest.txt: -------------------------------------------------------------------------------- 1 | Not a promoter manifest 2 | -------------------------------------------------------------------------------- /test-e2e/golden-images/content/foo/1.0-linux_amd64: -------------------------------------------------------------------------------- 1 | foo 1.0-linux_amd64 2 | -------------------------------------------------------------------------------- /test-e2e/golden-images/content/foo/1.0-linux_s390x: -------------------------------------------------------------------------------- 1 | foo 1.0-linux_s390x 2 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/empty/non-manifest.txt: -------------------------------------------------------------------------------- 1 | Not a promoter manifest 2 | -------------------------------------------------------------------------------- /promobot/testdata/files/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/promo-tools/HEAD/promobot/testdata/files/blue.png -------------------------------------------------------------------------------- /promobot/testdata/files/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/promo-tools/HEAD/promobot/testdata/files/red.png -------------------------------------------------------------------------------- /promobot/testdata/files/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/promo-tools/HEAD/promobot/testdata/files/green.png -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/invalid/empty/non-manifest.txt: -------------------------------------------------------------------------------- 1 | Not a promoter manifest 2 | -------------------------------------------------------------------------------- /promobot/testdata/manifests/onefile/filepromoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | filestores: 2 | - base: gs://src 3 | src: true 4 | - base: gs://dest 5 | -------------------------------------------------------------------------------- /promobot/testdata/manifests/manyfiles/filepromoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | filestores: 2 | - base: gs://src 3 | src: true 4 | - base: gs://dest 5 | -------------------------------------------------------------------------------- /canary/images/sigstore/images.yaml: -------------------------------------------------------------------------------- 1 | - name: cosign 2 | dmap: 3 | "sha256:a95d7c4ab27e48aaf89253e0703014709129f010578be809b6c95ccee908fa1b": ["v2.0.2"] 4 | -------------------------------------------------------------------------------- /promobot/testdata/manifests/manyfiles/files/red.yaml: -------------------------------------------------------------------------------- 1 | files: 2 | - name: red.png 3 | sha256: 5e6893c6c9ae8bf2a40b22b4274ca58d68c5614b476451a29859750bf434d6a8 4 | 5 | -------------------------------------------------------------------------------- /promobot/testdata/manifests/manyfiles/files/blue.yaml: -------------------------------------------------------------------------------- 1 | files: 2 | - name: blue.png 3 | sha256: 905fef7b0658ff5d266140d1cea1eb5b414393b4d0c7897b05beae78678395c3 4 | 5 | -------------------------------------------------------------------------------- /promobot/testdata/manifests/manyfiles/files/green.yaml: -------------------------------------------------------------------------------- 1 | files: 2 | - name: green.png 3 | sha256: 7e24ef9e8ed9454980182e787fc61dca44014571be346f3a5b341ce6c028e45d 4 | 5 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Community Code of Conduct 2 | 3 | Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) 4 | -------------------------------------------------------------------------------- /container-structure.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 2.0.0 2 | fileExistenceTests: 3 | - name: 'Docker configuration file' 4 | path: '/root/.docker/config.json' 5 | shouldExist: true 6 | -------------------------------------------------------------------------------- /promobot/testdata/manifests/manyprojects/manifests/project1/red.yaml: -------------------------------------------------------------------------------- 1 | files: 2 | - name: red.png 3 | sha256: 5e6893c6c9ae8bf2a40b22b4274ca58d68c5614b476451a29859750bf434d6a8 4 | 5 | -------------------------------------------------------------------------------- /promobot/testdata/manifests/manyprojects/manifests/project2/red.yaml: -------------------------------------------------------------------------------- 1 | files: 2 | - name: red.png 3 | sha256: 5e6893c6c9ae8bf2a40b22b4274ca58d68c5614b476451a29859750bf434d6a8 4 | 5 | -------------------------------------------------------------------------------- /test-e2e/cip-auditor/fixture/basic/images/bar/images.yaml: -------------------------------------------------------------------------------- 1 | - name: bar 2 | dmap: 3 | sha256:dd19dc426fa901c12e9a2eeeef8d9ad6c24f50840b8121ccffbba40b5500cb5b: 4 | - 1.0 5 | -------------------------------------------------------------------------------- /test-e2e/cip/fixture/recursive-thin/images/bar/images.yaml: -------------------------------------------------------------------------------- 1 | - name: bar 2 | dmap: 3 | sha256:dd19dc426fa901c12e9a2eeeef8d9ad6c24f50840b8121ccffbba40b5500cb5b: 4 | - 1.0 5 | -------------------------------------------------------------------------------- /image/manifest/testdata/singleton/images/a/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | "sha256:c3d310f4741b3642497da8826e0986db5e02afc9777a2b8e668c8e41034128c1": ["1.0"] 4 | -------------------------------------------------------------------------------- /promobot/testdata/manifests/manyprojects/manifests/project1/blue.yaml: -------------------------------------------------------------------------------- 1 | files: 2 | - name: blue.png 3 | sha256: 905fef7b0658ff5d266140d1cea1eb5b414393b4d0c7897b05beae78678395c3 4 | 5 | -------------------------------------------------------------------------------- /promobot/testdata/manifests/manyprojects/manifests/project1/green.yaml: -------------------------------------------------------------------------------- 1 | files: 2 | - name: green.png 3 | sha256: 7e24ef9e8ed9454980182e787fc61dca44014571be346f3a5b341ce6c028e45d 4 | 5 | -------------------------------------------------------------------------------- /promobot/testdata/manifests/manyprojects/manifests/project2/blue.yaml: -------------------------------------------------------------------------------- 1 | files: 2 | - name: blue.png 3 | sha256: 905fef7b0658ff5d266140d1cea1eb5b414393b4d0c7897b05beae78678395c3 4 | 5 | -------------------------------------------------------------------------------- /promobot/testdata/manifests/manyprojects/manifests/project2/green.yaml: -------------------------------------------------------------------------------- 1 | files: 2 | - name: green.png 3 | sha256: 7e24ef9e8ed9454980182e787fc61dca44014571be346f3a5b341ce6c028e45d 4 | 5 | -------------------------------------------------------------------------------- /promobot/testdata/manifests/manyprojects/filestores/project1/filepromoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | filestores: 2 | - base: gs://k8s-staging-project1 3 | src: true 4 | - base: gs://test-prod/project1 5 | -------------------------------------------------------------------------------- /promobot/testdata/manifests/manyprojects/filestores/project2/filepromoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | filestores: 2 | - base: gs://k8s-staging-project2 3 | src: true 4 | - base: gs://test-prod/project2 5 | -------------------------------------------------------------------------------- /test-e2e/cip-auditor/fixture/fatManifest/images/foo/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo 2 | dmap: 3 | # This is a fat manifest. 4 | sha256:b7efc8e6778e8e096d527862365ca8e7f351d1555e11bcf7d230a18b9ba72b34: 5 | - 1.0 6 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/basic-thin/images/a/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": ["1.0"] 4 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/basic-thin/images/b/images.yaml: -------------------------------------------------------------------------------- 1 | - name: bar-controller 2 | dmap: 3 | "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": ["1.0"] 4 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/basic-thin/images/c/images.yaml: -------------------------------------------------------------------------------- 1 | - name: cat-controller 2 | dmap: 3 | "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc": ["1.0"] 4 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/basic-thin/images/d/images.yaml: -------------------------------------------------------------------------------- 1 | - name: qux-controller 2 | dmap: 3 | "sha256:0000000000000000000000000000000000000000000000000000000000000000": ["1.0"] 4 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/singleton/images/a/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | "sha256:c3d310f4741b3642497da8826e0986db5e02afc9777a2b8e668c8e41034128c1": ["1.0"] 4 | -------------------------------------------------------------------------------- /canary/manifests/k8s-staging-bom/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/k8s-staging-bom 3 | src: true 4 | - name: us.gcr.io/k8s-cip-test-prod/canary/bom 5 | - name: us.gcr.io/k8s-cip-test-prod/canary/bom-mirror 6 | -------------------------------------------------------------------------------- /canary/manifests/sigstore/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/projectsigstore 3 | src: true 4 | - name: us.gcr.io/k8s-cip-test-prod/canary/sigstore 5 | - name: us.gcr.io/k8s-cip-test-prod/canary/sigstore-mirror 6 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/multiple-rebases/images/a/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": ["1.0"] 4 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/multiple-rebases/images/b/images.yaml: -------------------------------------------------------------------------------- 1 | - name: bar-controller 2 | dmap: 3 | "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": ["1.0"] 4 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/singleton/images/a/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | "sha256:c3d310f4741b3642497da8826e0986db5e02afc9777a2b8e668c8e41034128c1": ["1.0"] 4 | -------------------------------------------------------------------------------- /test-e2e/cip-auditor/fixture/fatManifest-subproject-different-prefix/images/foo/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo 2 | dmap: 3 | # This is a fat manifest. 4 | sha256:b7efc8e6778e8e096d527862365ca8e7f351d1555e11bcf7d230a18b9ba72b34: 5 | - 1.0 6 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/multiple-rebases/images/a/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": ["1.0"] 4 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/multiple-rebases/images/b/images.yaml: -------------------------------------------------------------------------------- 1 | - name: bar-controller 2 | dmap: 3 | "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": ["1.0"] 4 | -------------------------------------------------------------------------------- /canary/images/k8s-staging-bom/images.yaml: -------------------------------------------------------------------------------- 1 | - name: bom 2 | dmap: 3 | "sha256:631ff0f765b4b1ff0b3c81d73698f557f2291aebc822eedf4d265a9ec611af42": ["v0.3.0-rc1"] 4 | "sha256:d7223c9f621d98aac275b2b03a102d5c436718b4a26e167cffc691f977653d9a": ["v0.3.0"] 5 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/overlapping-src-registries/images/a/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": ["1.0"] 4 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/overlapping-src-registries/images/b/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": ["1.1"] 4 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/invalid/malformed-directory-tree-structure/images/a/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": ["1.0"] 4 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/invalid/malformed-directory-tree-structure-nested/images/a/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": ["1.0"] 4 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/overlapping-destination-vertices-same-digest/images/a/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": ["1.0"] 4 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/overlapping-destination-vertices-same-digest/images/b/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": ["1.0"] 4 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/invalid/malformed-directory-tree-structure-nested/manifests/images/c/images.yaml: -------------------------------------------------------------------------------- 1 | - name: c-controller 2 | dmap: 3 | "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc": ["1.0"] 4 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/invalid/overlapping-destination-vertices-different-digest/images/a/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": ["1.0"] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.#* 2 | *.swp 3 | 4 | # Bazel (to ensure bazel configs are not recommitted to repo) 5 | *.bazel 6 | *.bzl 7 | .bazel* 8 | bazel-* 9 | WORKSPACE 10 | 11 | # downloaded and built binaries 12 | bin 13 | # tmp files 14 | tmp/ 15 | 16 | # vscode settings 17 | .vscode 18 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/malformed-directory-tree-structure-bad-prefix-is-ignored/images/a/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": ["1.0"] 4 | -------------------------------------------------------------------------------- /cmd/cip-mm/README.md: -------------------------------------------------------------------------------- 1 | # cip-mm 2 | 3 | `cip-mm` is deprecated and will be removed in a future release. 4 | 5 | The deprecated functionality will temporarily remain for existing consumers 6 | (as `kpromo mm`), but please begin to use 7 | [`kpromo pr`](/docs/promotion-pull-requests.md) instead. 8 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | 3 | approvers: 4 | - sig-release-leads 5 | - release-engineering-approvers 6 | - promo-tools-approvers 7 | reviewers: 8 | - release-engineering-reviewers 9 | - promo-tools-reviewers 10 | 11 | labels: 12 | - sig/release 13 | - area/artifacts 14 | - area/release-eng 15 | -------------------------------------------------------------------------------- /promobot/testdata/files-manifest.yaml: -------------------------------------------------------------------------------- 1 | files: 2 | - name: blue.png 3 | sha256: 905fef7b0658ff5d266140d1cea1eb5b414393b4d0c7897b05beae78678395c3 4 | - name: green.png 5 | sha256: 7e24ef9e8ed9454980182e787fc61dca44014571be346f3a5b341ce6c028e45d 6 | - name: red.png 7 | sha256: 5e6893c6c9ae8bf2a40b22b4274ca58d68c5614b476451a29859750bf434d6a8 8 | -------------------------------------------------------------------------------- /promobot/testdata/manifests/onefile/files.yaml: -------------------------------------------------------------------------------- 1 | files: 2 | - name: blue.png 3 | sha256: 905fef7b0658ff5d266140d1cea1eb5b414393b4d0c7897b05beae78678395c3 4 | - name: green.png 5 | sha256: 7e24ef9e8ed9454980182e787fc61dca44014571be346f3a5b341ce6c028e45d 6 | - name: red.png 7 | sha256: 5e6893c6c9ae8bf2a40b22b4274ca58d68c5614b476451a29859750bf434d6a8 8 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/invalid/overlapping-destination-vertices-different-digest/images/b/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo-controller 2 | dmap: 3 | # This sha conflicts with a's manifest that has "aaaa..." point to the "1.0" tag. 4 | "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": ["1.0"] 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest a feature for Kubernetes container image promoter tooling 4 | labels: kind/feature, sig/release, area/release-eng 5 | 6 | --- 7 | 8 | 9 | #### What would you like to be added: 10 | 11 | #### Why is this needed: 12 | -------------------------------------------------------------------------------- /image/manifest/testdata/singleton/manifests/a/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/malformed-directory-tree-structure-bad-prefix-is-ignored/README.md: -------------------------------------------------------------------------------- 1 | This test has a specal case where the folder "manifest" has a promoter manifest 2 | in it. This should succeed because we check for manifest files under the 3 | toplevel "manifests" folder (with in "s"), and ignore other folders. 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | labels: 8 | - "area/dependency" 9 | - "release-note-none" 10 | - "ok-to-test" 11 | open-pull-requests-limit: 10 12 | groups: 13 | gomod: 14 | update-types: 15 | - "patch" 16 | -------------------------------------------------------------------------------- /test-e2e/cip-auditor/fixture/basic/manifests/bar/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/k8s-gcr-audit-test-prod/golden-bar 3 | service-account: k8s-infra-gcr-promoter@k8s-gcr-audit-test-prod.iam.gserviceaccount.com 4 | src: true 5 | - name: us.gcr.io/k8s-gcr-audit-test-prod/golden-bar 6 | service-account: k8s-infra-gcr-promoter@k8s-gcr-audit-test-prod.iam.gserviceaccount.com 7 | -------------------------------------------------------------------------------- /test-e2e/cip-auditor/fixture/basic/manifests/foo/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/k8s-gcr-audit-test-prod/golden-foo 3 | service-account: k8s-infra-gcr-promoter@k8s-gcr-audit-test-prod.iam.gserviceaccount.com 4 | src: true 5 | - name: us.gcr.io/k8s-gcr-audit-test-prod/golden-foo 6 | service-account: k8s-infra-gcr-promoter@k8s-gcr-audit-test-prod.iam.gserviceaccount.com 7 | -------------------------------------------------------------------------------- /test-e2e/cip-auditor/fixture/fatManifest/manifests/foo/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/k8s-gcr-audit-test-prod/golden-foo 3 | service-account: k8s-infra-gcr-promoter@k8s-gcr-audit-test-prod.iam.gserviceaccount.com 4 | src: true 5 | - name: us.gcr.io/k8s-gcr-audit-test-prod/golden-foo 6 | service-account: k8s-infra-gcr-promoter@k8s-gcr-audit-test-prod.iam.gserviceaccount.com 7 | -------------------------------------------------------------------------------- /test-e2e/cip-auditor/fixture/fatManifest-subproject-different-prefix/manifests/foo/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/k8s-gcr-audit-test-prod 3 | service-account: k8s-infra-gcr-promoter@k8s-gcr-audit-test-prod.iam.gserviceaccount.com 4 | src: true 5 | - name: us.gcr.io/k8s-gcr-audit-test-prod/subproject-foo 6 | service-account: k8s-infra-gcr-promoter@k8s-gcr-audit-test-prod.iam.gserviceaccount.com 7 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/basic-thin/manifests/d/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/qux-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/singleton/manifests/a/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/singleton/manifests/a/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/multiple-rebases/manifests/a/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod/foo 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod/foo 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod/foo 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/multiple-rebases/manifests/b/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/bar-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod/bar 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod/bar 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod/bar 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/overlapping-src-registries/manifests/a/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/overlapping-src-registries/manifests/b/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /promobot/testdata/expected/manyfiles.yaml: -------------------------------------------------------------------------------- 1 | - files: 2 | - name: blue.png 3 | sha256: 905fef7b0658ff5d266140d1cea1eb5b414393b4d0c7897b05beae78678395c3 4 | - name: green.png 5 | sha256: 7e24ef9e8ed9454980182e787fc61dca44014571be346f3a5b341ce6c028e45d 6 | - name: red.png 7 | sha256: 5e6893c6c9ae8bf2a40b22b4274ca58d68c5614b476451a29859750bf434d6a8 8 | filestores: 9 | - base: gs://src 10 | src: true 11 | - base: gs://dest 12 | -------------------------------------------------------------------------------- /promobot/testdata/expected/onefile.yaml: -------------------------------------------------------------------------------- 1 | - files: 2 | - name: blue.png 3 | sha256: 905fef7b0658ff5d266140d1cea1eb5b414393b4d0c7897b05beae78678395c3 4 | - name: green.png 5 | sha256: 7e24ef9e8ed9454980182e787fc61dca44014571be346f3a5b341ce6c028e45d 6 | - name: red.png 7 | sha256: 5e6893c6c9ae8bf2a40b22b4274ca58d68c5614b476451a29859750bf434d6a8 8 | filestores: 9 | - base: gs://src 10 | src: true 11 | - base: gs://dest 12 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/multiple-rebases/manifests/a/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod/foo 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod/foo 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod/foo 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/multiple-rebases/manifests/b/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/bar-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod/bar 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod/bar 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod/bar 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/invalid/malformed-directory-tree-structure-nested/README.md: -------------------------------------------------------------------------------- 1 | This test has a specal case where the folder "c" has a promoter manifest in it, 2 | and also has a folder at "../../images/c/images.yaml" (relative to its 3 | location). This should still fail because we check for images files under the 4 | toplevel "images" folder, not just any folder 2 levels up from the promoter 5 | manifest. 6 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/invalid/malformed-directory-tree-structure/manifests/a/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/invalid/malformed-directory-tree-structure/manifests/b/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/basic-thin/manifests/a/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | imagesPath: "../../images/a.yaml" 12 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/basic-thin/manifests/b/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/bar-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | imagesPath: "../../images/b.yaml" 12 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestParseThinManifestsFromDir/basic-thin/manifests/c/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/cat-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | imagesPath: "../../../images/bc.yaml" 12 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/invalid/malformed-directory-tree-structure-nested/manifests/a/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/invalid/malformed-directory-tree-structure-nested/manifests/b/c/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/c-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/overlapping-destination-vertices-same-digest/manifests/a/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/overlapping-destination-vertices-same-digest/manifests/b/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/bar-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/invalid/overlapping-destination-vertices-different-digest/manifests/a/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/invalid/overlapping-destination-vertices-different-digest/manifests/b/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /test-e2e/cip-auditor/fixture/basic/images/foo/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo 2 | dmap: 3 | sha256:b7efc8e6778e8e096d527862365ca8e7f351d1555e11bcf7d230a18b9ba72b34: 4 | - 1.0 5 | sha256:64b870d27522b175b4778d4bf57b5f7f2495db4269bf227aa193f435272644e2: 6 | - 1.0-linux_amd64 7 | sha256:d66f4b0bab4061ef6244f93bea2d414923e7504d92c77a91998c23f909033b02: 8 | - 1.0-linux_s390x 9 | sha256:27ba895d293e5e3192b2bb57f0126f923b48d20221bb017e899ca3f5af74f738: [] 10 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/malformed-directory-tree-structure-bad-prefix-is-ignored/manifest/b/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/bbb-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/inventory_test/TestValidateThinManifestsFromDir/valid/malformed-directory-tree-structure-bad-prefix-is-ignored/manifests/a/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/foo-staging 3 | service-account: sa@robot.com 4 | src: true 5 | - name: us.gcr.io/some-prod 6 | service-account: sa@robot.com 7 | - name: eu.gcr.io/some-prod 8 | service-account: sa@robot.com 9 | - name: asia.gcr.io/some-prod 10 | service-account: sa@robot.com 11 | -------------------------------------------------------------------------------- /test-e2e/cip/fixture/recursive-thin/images/foo/images.yaml: -------------------------------------------------------------------------------- 1 | - name: foo 2 | dmap: 3 | sha256:b7efc8e6778e8e096d527862365ca8e7f351d1555e11bcf7d230a18b9ba72b34: 4 | - 1.0 5 | sha256:64b870d27522b175b4778d4bf57b5f7f2495db4269bf227aa193f435272644e2: 6 | - 1.0-linux_amd64 7 | sha256:d66f4b0bab4061ef6244f93bea2d414923e7504d92c77a91998c23f909033b02: 8 | - 1.0-linux_s390x 9 | sha256:27ba895d293e5e3192b2bb57f0126f923b48d20221bb017e899ca3f5af74f738: [] 10 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright YEAR The Kubernetes 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 | -------------------------------------------------------------------------------- /SECURITY_CONTACTS: -------------------------------------------------------------------------------- 1 | # Defined below are the security contacts for this repo. 2 | # 3 | # They are the contact points for the Security Response Committee to reach out 4 | # to for triaging and handling of incoming issues. 5 | # 6 | # The below names agree to abide by the 7 | # [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy) 8 | # and will be removed and replaced if they violate that agreement. 9 | # 10 | # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE 11 | # INSTRUCTIONS AT https://kubernetes.io/security/ 12 | 13 | cpanato 14 | jeremyrickard 15 | justaugustus 16 | listx 17 | puerco 18 | saschagrunert 19 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.generatego.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes 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 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.py.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.sh.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.Dockerfile.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.Makefile.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Information about supported Kubernetes versions can be found on the 6 | [Kubernetes version and version skew support policy] page on the Kubernetes website. 7 | 8 | ## Reporting a Vulnerability 9 | 10 | Instructions for reporting a vulnerability can be found on the 11 | [Kubernetes Security and Disclosure Information] page. 12 | 13 | [Kubernetes version and version skew support policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions 14 | [Kubernetes Security and Disclosure Information]: https://kubernetes.io/docs/reference/issues-security/security/#report-a-vulnerability 15 | -------------------------------------------------------------------------------- /test-e2e/cip/fixture/recursive-thin/manifests/bar/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/k8s-staging-cip-test/golden-bar 3 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 4 | src: true 5 | - name: us.gcr.io/k8s-cip-test-prod/e2e/golden-bar 6 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 7 | - name: eu.gcr.io/k8s-cip-test-prod/e2e/golden-bar 8 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 9 | - name: asia.gcr.io/k8s-cip-test-prod/e2e/golden-bar 10 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 11 | imagesPath: "../../images/bar/images.yaml" 12 | -------------------------------------------------------------------------------- /test-e2e/cip/fixture/recursive-thin/manifests/foo/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/k8s-staging-cip-test/golden-foo 3 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 4 | src: true 5 | - name: us.gcr.io/k8s-cip-test-prod/e2e/golden-foo 6 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 7 | - name: eu.gcr.io/k8s-cip-test-prod/e2e/golden-foo 8 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 9 | - name: asia.gcr.io/k8s-cip-test-prod/e2e/golden-foo 10 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 11 | imagesPath: "../../images/foo/images.yaml" 12 | -------------------------------------------------------------------------------- /internal/legacy/cli/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 cli 18 | 19 | // TODO: Refactor cli import functions into this file 20 | -------------------------------------------------------------------------------- /hack/verify-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2021 The Kubernetes 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 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | make build 22 | -------------------------------------------------------------------------------- /hack/verify-go-mod.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes 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 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | go mod tidy 22 | git diff --exit-code 23 | -------------------------------------------------------------------------------- /hack/verify-mocks.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2021 The Kubernetes 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 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | make update-mocks 22 | git diff --exit-code 23 | -------------------------------------------------------------------------------- /cmd/kpromo/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 | //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate 18 | 19 | package main 20 | 21 | import "sigs.k8s.io/promo-tools/v4/cmd/kpromo/cmd" 22 | 23 | func main() { 24 | cmd.Execute() 25 | } 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug encountered while using container image promoter tooling 4 | labels: kind/bug, sig/release, area/release-eng 5 | 6 | --- 7 | 8 | 12 | 13 | #### What happened: 14 | 15 | #### What you expected to happen: 16 | 17 | #### How to reproduce it (as minimally and precisely as possible): 18 | 19 | #### Anything else we need to know?: 20 | 21 | #### Environment: 22 | 23 | - Cloud provider or hardware configuration: 24 | - OS (e.g: `cat /etc/os-release`): 25 | - Kernel (e.g. `uname -a`): 26 | - Others: 27 | -------------------------------------------------------------------------------- /internal/tools.go: -------------------------------------------------------------------------------- 1 | // +build tools 2 | 3 | /* 4 | Copyright 2021 The Kubernetes Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // This is used to import things required by build scripts, to force `go mod` to see them as dependencies 20 | 21 | package internal 22 | 23 | import ( 24 | _ "github.com/maxbrunsfeld/counterfeiter/v6" 25 | ) 26 | -------------------------------------------------------------------------------- /promobot/testdata/expected/manyprojects.yaml: -------------------------------------------------------------------------------- 1 | - files: 2 | - name: blue.png 3 | sha256: 905fef7b0658ff5d266140d1cea1eb5b414393b4d0c7897b05beae78678395c3 4 | - name: green.png 5 | sha256: 7e24ef9e8ed9454980182e787fc61dca44014571be346f3a5b341ce6c028e45d 6 | - name: red.png 7 | sha256: 5e6893c6c9ae8bf2a40b22b4274ca58d68c5614b476451a29859750bf434d6a8 8 | filestores: 9 | - base: gs://k8s-staging-project1 10 | src: true 11 | - base: gs://test-prod/project1 12 | - files: 13 | - name: blue.png 14 | sha256: 905fef7b0658ff5d266140d1cea1eb5b414393b4d0c7897b05beae78678395c3 15 | - name: green.png 16 | sha256: 7e24ef9e8ed9454980182e787fc61dca44014571be346f3a5b341ce6c028e45d 17 | - name: red.png 18 | sha256: 5e6893c6c9ae8bf2a40b22b4274ca58d68c5614b476451a29859750bf434d6a8 19 | filestores: 20 | - base: gs://k8s-staging-project2 21 | src: true 22 | - base: gs://test-prod/project2 23 | -------------------------------------------------------------------------------- /cmd/kpromo/cmd/run/run.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 run 18 | 19 | import ( 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | // RunCmd is a kpromo subcommand which just holds further subcommands. 24 | var RunCmd = &cobra.Command{ 25 | Use: "run", 26 | Short: "Run artifact promotion", 27 | SilenceUsage: true, 28 | SilenceErrors: true, 29 | } 30 | -------------------------------------------------------------------------------- /internal/legacy/remotemanifest/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 remotemanifest 18 | 19 | import "sigs.k8s.io/promo-tools/v4/internal/legacy/dockerregistry/schema" 20 | 21 | // Facility requires a single method, called Fetch(), which corresponds to 22 | // fetching a set of promoter manifests. 23 | type Facility interface { 24 | Fetch() ([]schema.Manifest, error) 25 | } 26 | -------------------------------------------------------------------------------- /cmd/kpromo/cmd/manifest/manifest.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 manifest 18 | 19 | import ( 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | // ManifestCmd is a kpromo subcommand which just holds further subcommands. 24 | var ManifestCmd = &cobra.Command{ 25 | Use: "manifest", 26 | Short: "Generate/modify a manifest for artifact promotion", 27 | SilenceUsage: true, 28 | SilenceErrors: true, 29 | } 30 | -------------------------------------------------------------------------------- /internal/version/version_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 version 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/require" 23 | ) 24 | 25 | func TestVersionText(t *testing.T) { 26 | sut := Get() 27 | require.NotEmpty(t, sut.String()) 28 | } 29 | 30 | func TestVersionJSON(t *testing.T) { 31 | sut, err := Get().JSONString() 32 | require.NoError(t, err) 33 | require.NotEmpty(t, sut) 34 | } 35 | -------------------------------------------------------------------------------- /image/consts/consts.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes 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 consts 18 | 19 | const ( 20 | // Production registry root URL. 21 | ProdRegistry = "registry.k8s.io" 22 | 23 | // Staging repository root URL prefix. 24 | StagingRepoPrefix = "gcr.io/k8s-staging-" 25 | 26 | // The suffix of the default image repository to promote images from 27 | // i.e., gcr.io/- 28 | // e.g., gcr.io/k8s-staging-foo. 29 | StagingRepoSuffix = "kubernetes" 30 | ) 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/cut-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Cut a new promo-tools release 3 | about: Create a tracking issue for a promo-tools release cut 4 | title: Release k-sigs/promo-tools@vX.Y.Z 5 | labels: sig/release, area/release-eng 6 | 7 | --- 8 | 9 | ### vX.Y.Z 10 | 11 | Issue track for the upcoming release of promo-tools X.Y.Z. 12 | 13 | 14 | 15 | Previous promo-tools release cut issue: 16 | 17 | - [ ] Release Prep: 18 | - [ ] Release: 19 | - [ ] Image promotion: 20 | - [ ] Rollout: 21 | 22 | cc @kubernetes-sigs/release-engineering 23 | -------------------------------------------------------------------------------- /test-e2e/cip/fixture/sanity/promoter-manifest.yaml: -------------------------------------------------------------------------------- 1 | registries: 2 | - name: gcr.io/k8s-staging-cip-test/golden-foo 3 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 4 | src: true 5 | - name: us.gcr.io/k8s-cip-test-prod/e2e/some/subdir 6 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 7 | - name: eu.gcr.io/k8s-cip-test-prod/e2e/some/subdir 8 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 9 | - name: asia.gcr.io/k8s-cip-test-prod/e2e/some/subdir 10 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 11 | 12 | images: 13 | - name: foo 14 | dmap: 15 | sha256:b7efc8e6778e8e096d527862365ca8e7f351d1555e11bcf7d230a18b9ba72b34: 16 | - 1.0 17 | sha256:64b870d27522b175b4778d4bf57b5f7f2495db4269bf227aa193f435272644e2: 18 | - 1.0-linux_amd64 19 | sha256:d66f4b0bab4061ef6244f93bea2d414923e7504d92c77a91998c23f909033b02: 20 | - 1.0-linux_s390x 21 | sha256:27ba895d293e5e3192b2bb57f0126f923b48d20221bb017e899ca3f5af74f738: [] 22 | -------------------------------------------------------------------------------- /hack/verify-boilerplate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes 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 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | VERSION=v0.2.2 22 | URL_BASE=https://raw.githubusercontent.com/kubernetes/repo-infra 23 | URL=$URL_BASE/$VERSION/hack/verify_boilerplate.py 24 | BIN_DIR=bin 25 | SCRIPT=$BIN_DIR/verify_boilerplate.py 26 | 27 | if [[ ! -f $SCRIPT ]]; then 28 | mkdir -p $BIN_DIR 29 | curl -sfL $URL -o $SCRIPT 30 | chmod +x $SCRIPT 31 | fi 32 | 33 | $SCRIPT --boilerplate-dir hack/boilerplate 34 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows 28 | [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/). 29 | -------------------------------------------------------------------------------- /internal/legacy/logclient/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 logclient 18 | 19 | import ( 20 | "io" 21 | "log" 22 | ) 23 | 24 | // These constants refer to the logging levels. 25 | const ( 26 | IndexLogInfo = iota 27 | IndexLogError 28 | IndexLogAlert 29 | ) 30 | 31 | // GetLoggers extracts 3 loggers, corresponding to the logging levels defined 32 | // above. 33 | type GetLoggers interface { 34 | GetInfoLogger() *log.Logger 35 | GetErrorLogger() *log.Logger 36 | GetAlertLogger() *log.Logger 37 | } 38 | 39 | // LoggingFacility bundles 3 loggers together. 40 | type LoggingFacility interface { 41 | GetLoggers 42 | io.Closer 43 | } 44 | -------------------------------------------------------------------------------- /promoter/image/checkresults/checkresults.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes 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 checkresults 18 | 19 | type CheckList struct { 20 | Signed []string 21 | Missing []string 22 | } 23 | 24 | type Signature map[string]CheckList 25 | 26 | func (s *Signature) TotalPartial() int { 27 | total := 0 28 | for _, list := range *s { 29 | if len(list.Signed) > 0 && len(list.Missing) > 0 { 30 | total++ 31 | } 32 | } 33 | return total 34 | } 35 | 36 | func (s *Signature) TotalUnsigned() int { 37 | total := 0 38 | for _, list := range *s { 39 | if len(list.Missing) > 0 && len(list.Signed) == 0 { 40 | total++ 41 | } 42 | } 43 | return total 44 | } 45 | -------------------------------------------------------------------------------- /OWNERS_ALIASES: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | 3 | aliases: 4 | sig-release-leads: 5 | - cpanato # SIG Technical Lead 6 | - jeremyrickard # SIG Chair 7 | - justaugustus # SIG Chair 8 | - puerco # SIG Technical Lead 9 | - saschagrunert # SIG Chair 10 | - Verolop # SIG Technical Lead 11 | release-engineering-approvers: 12 | - cpanato # subproject owner / Release Manager 13 | - jeremyrickard # subproject owner / Release Manager 14 | - justaugustus # subproject owner / Release Manager 15 | - palnabarun # Release Manager 16 | - puerco # subproject owner / Release Manager 17 | - saschagrunert # subproject owner / Release Manager 18 | - xmudrii # Release Manager 19 | - Verolop # subproject owner / Release Manager 20 | release-engineering-reviewers: 21 | - ameukam # Release Manager Associate 22 | - cici37 # Release Manager Associate 23 | - jimangel # Release Manager Associate 24 | - jrsapi # Release Manager Associate 25 | - salaxander # Release Manager Associate 26 | promo-tools-approvers: 27 | - cpanato 28 | - jeremyrickard 29 | - justaugustus 30 | - puerco 31 | - saschagrunert 32 | promo-tools-reviewers: 33 | - cpanato 34 | - jeremyrickard 35 | - justaugustus 36 | - puerco 37 | - saschagrunert 38 | -------------------------------------------------------------------------------- /internal/legacy/remotemanifest/fake.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 remotemanifest 18 | 19 | import "sigs.k8s.io/promo-tools/v4/internal/legacy/dockerregistry/schema" 20 | 21 | // Fake is a fake remote manifest. It is fake in the sense that it 22 | // will never fetch anything from any remote. 23 | type Fake struct { 24 | manifests []schema.Manifest 25 | } 26 | 27 | // Fetch just returns the manifests that were set in NewFakeRemoteManifest. 28 | func (remote *Fake) Fetch() ([]schema.Manifest, error) { 29 | return remote.manifests, nil 30 | } 31 | 32 | // NewFake creates a new Fake. 33 | func NewFake(manifests []schema.Manifest) *Fake { 34 | remote := Fake{} 35 | 36 | remote.manifests = manifests 37 | 38 | return &remote 39 | } 40 | -------------------------------------------------------------------------------- /types/image/image.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kubernetes 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 image 18 | 19 | // UserAgent header to be used in the requests. 20 | const UserAgent = "kpromo" 21 | 22 | // Name can be just the bare name itself (e.g., "addon-builder" in 23 | // "gcr.io/k8s-image-staging/addon-builder") or the prefix + name 24 | // ("foo/bar/baz/quux" in "gcr.io/hello/foo/bar/baz/quux"). 25 | type Name string 26 | 27 | // Registry is the leading part of an image name that includes the domain; 28 | // it is everything that is not the actual image name itself. e.g., 29 | // "gcr.io/google-containers". 30 | type Registry string 31 | 32 | // Digest is a string that contains the SHA256 hash of a Docker container image. 33 | type Digest string 34 | 35 | // Tag is a Docker tag. 36 | type Tag string 37 | -------------------------------------------------------------------------------- /internal/legacy/json/json.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 json 18 | 19 | import ( 20 | "io" 21 | 22 | yaml "gopkg.in/yaml.v2" 23 | ) 24 | 25 | // Object is a JSON object. 26 | type Object map[string]interface{} 27 | 28 | // Objects is a slice of Object values. 29 | type Objects []Object 30 | 31 | // Consume decodes JSON from a given io.Reader handle. 32 | func Consume(h io.Reader) (Objects, error) { 33 | // Generic-looking type to hold whatever JSON objects we get as a stream 34 | // (that's why it's a slice, not just a plain map). 35 | var m Objects 36 | decoder := yaml.NewDecoder(h) 37 | for { 38 | err := decoder.Decode(&m) 39 | if err != nil { 40 | if err == io.EOF { 41 | break 42 | } 43 | return nil, err 44 | } 45 | } 46 | return m, nil 47 | } 48 | -------------------------------------------------------------------------------- /hack/verify-dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes 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 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | VERSION=v0.5.4 22 | REPO_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. 23 | 24 | # Ensure that we find the binaries we build before anything else. 25 | gobin=${GOBIN:-$(go env GOBIN)} 26 | if [[ -z $gobin ]]; then 27 | gobin="$(go env GOPATH)/bin" 28 | fi 29 | PATH="${gobin}:${PATH}" 30 | 31 | # Install zeitgeist 32 | cd "${REPO_ROOT}/internal" 33 | GO111MODULE=on go install sigs.k8s.io/zeitgeist@"${VERSION}" 34 | cd - 35 | 36 | # Prefer full path for running zeitgeist 37 | ZEITGEIST_BIN="$(which zeitgeist)" 38 | 39 | "${ZEITGEIST_BIN}" validate \ 40 | --local-only \ 41 | --base-path "${REPO_ROOT}" \ 42 | --config "${REPO_ROOT}"/dependencies.yaml 43 | -------------------------------------------------------------------------------- /promoter/file/interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 file 18 | 19 | import ( 20 | "context" 21 | 22 | api "sigs.k8s.io/promo-tools/v4/api/files" 23 | ) 24 | 25 | //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate 26 | 27 | // SyncFileOp defines a synchronization operation. 28 | // 29 | //counterfeiter:generate . SyncFileOp 30 | type SyncFileOp interface { 31 | Run(ctx context.Context) error 32 | } 33 | 34 | // Provider defines a file provider, able to work with GCS or S3. 35 | type Provider interface { 36 | // Scheme returns the URI scheme we handle. 37 | Scheme() string 38 | 39 | // OpenFilestore opens a handle to the specified filestore. 40 | OpenFilestore(ctx context.Context, filestore *api.Filestore, useServiceAccount, confirm bool) (syncFilestore, error) 41 | } 42 | -------------------------------------------------------------------------------- /internal/legacy/report/gcp.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 report 18 | 19 | import ( 20 | "context" 21 | 22 | "cloud.google.com/go/errorreporting" 23 | "github.com/sirupsen/logrus" 24 | ) 25 | 26 | // NewGcpErrorReportingClient returns a new Stackdriver Error Reporting client. 27 | func NewGcpErrorReportingClient( 28 | projectID, serviceName string, 29 | ) *errorreporting.Client { 30 | ctx := context.Background() 31 | 32 | erc, err := errorreporting.NewClient(ctx, projectID, errorreporting.Config{ 33 | ServiceName: serviceName, 34 | OnError: func(err error) { 35 | logrus.Errorf( 36 | "could not log error to GCP Stackdriver Error Reporting: %v", 37 | err, 38 | ) 39 | }, 40 | }) 41 | if err != nil { 42 | logrus.Fatalf("Failed to create errorreporting client: %v", err) 43 | } 44 | 45 | return erc 46 | } 47 | -------------------------------------------------------------------------------- /internal/legacy/stream/fake.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 stream 18 | 19 | import ( 20 | "bytes" 21 | "io" 22 | "strings" 23 | ) 24 | 25 | // Fake is a predefined stream (set with Bytes). 26 | type Fake struct { 27 | stream io.Reader 28 | Bytes []byte 29 | } 30 | 31 | // Produce a fake stream on stdout (and an empty stderr). Unlike a real stream, 32 | // this does not call a subprocess --- instead it just provides a predefined 33 | // stream (Bytes) to create an io.Reader for stdout. The stderr stream is empty. 34 | func (producer *Fake) Produce() (stream, blankStderr io.Reader, err error) { 35 | producer.stream = bytes.NewReader(producer.Bytes) 36 | blankStderr = strings.NewReader("") 37 | return producer.stream, blankStderr, nil 38 | } 39 | 40 | // Close does nothing, as there is no actual subprocess to close. 41 | func (producer *Fake) Close() error { 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/registry/context.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kubernetes 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 registry 18 | 19 | import ( 20 | "errors" 21 | 22 | "sigs.k8s.io/promo-tools/v4/internal/legacy/gcloud" 23 | "sigs.k8s.io/promo-tools/v4/types/image" 24 | ) 25 | 26 | // Context holds information about a registry, to be written in a 27 | // manifest file. 28 | type Context struct { 29 | Name image.Registry `yaml:"name,omitempty"` 30 | ServiceAccount string `yaml:"service-account,omitempty"` 31 | Token gcloud.Token `yaml:"-"` 32 | Src bool `yaml:"src,omitempty"` 33 | } 34 | 35 | // GetSrcRegistry gets the source registry. 36 | func GetSrcRegistry(rcs []Context) (*Context, error) { 37 | for _, registry := range rcs { 38 | if registry.Src { 39 | return ®istry, nil 40 | } 41 | } 42 | 43 | return nil, errors.New("could not find source registry") 44 | } 45 | -------------------------------------------------------------------------------- /internal/promoter/image/securityscan.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kubernetes 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 imagepromoter 18 | 19 | import ( 20 | "fmt" 21 | 22 | reg "sigs.k8s.io/promo-tools/v4/internal/legacy/dockerregistry" 23 | options "sigs.k8s.io/promo-tools/v4/promoter/image/options" 24 | ) 25 | 26 | // ScanEdges runs the vulnerability scans on the new images 27 | // detected by the promoter. 28 | func (di *DefaultPromoterImplementation) ScanEdges( 29 | opts *options.Options, sc *reg.SyncContext, 30 | promotionEdges map[reg.PromotionEdge]interface{}, 31 | ) error { 32 | if err := sc.RunChecks( 33 | []reg.PreCheck{ 34 | reg.MKImageVulnCheck( 35 | sc, 36 | promotionEdges, 37 | opts.SeverityThreshold, 38 | nil, 39 | ), 40 | }, 41 | ); err != nil { 42 | return fmt.Errorf("checking image vulnerabilities: %w", err) 43 | } 44 | di.PrintSection("END (VULNSCAN)", opts.Confirm) 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /promoter/file/token.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 file 18 | 19 | import ( 20 | "sync" 21 | 22 | "github.com/sirupsen/logrus" 23 | "golang.org/x/oauth2" 24 | 25 | "sigs.k8s.io/promo-tools/v4/internal/legacy/gcloud" 26 | ) 27 | 28 | // gcloudTokenSource implements oauth2.TokenSource. 29 | type gcloudTokenSource struct { 30 | mutex sync.Mutex 31 | ServiceAccount string 32 | } 33 | 34 | // Token implements TokenSource.Token. 35 | func (s *gcloudTokenSource) Token() (*oauth2.Token, error) { 36 | s.mutex.Lock() 37 | defer s.mutex.Unlock() 38 | 39 | logrus.Infof("getting service-account-token for %q", s.ServiceAccount) 40 | 41 | token, err := gcloud.GetServiceAccountToken(s.ServiceAccount, true) 42 | if err != nil { 43 | logrus.Warnf("failed to get service-account-token for %q: %v", 44 | s.ServiceAccount, err) 45 | return nil, err 46 | } 47 | return &oauth2.Token{ 48 | AccessToken: string(token), 49 | }, nil 50 | } 51 | -------------------------------------------------------------------------------- /internal/legacy/timewrapper/timewrapper.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes 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 timewrapper 18 | 19 | import ( 20 | "time" 21 | ) 22 | 23 | // Time groups the functions mocked by FakeTime. 24 | type Time interface { 25 | Now() time.Time 26 | Sleep(d time.Duration) 27 | } 28 | 29 | // RealTime is a wrapper for actual time functions. 30 | type RealTime struct{} 31 | 32 | // Now simply calls time.Now(). 33 | func (RealTime) Now() time.Time { return time.Now() } 34 | 35 | // Sleep simply calls time.Sleep(d), using the given duration. 36 | func (RealTime) Sleep(d time.Duration) { time.Sleep(d) } 37 | 38 | // FakeTime holds the global fake time. 39 | type FakeTime struct { 40 | Time time.Time 41 | } 42 | 43 | // Now returns the global fake time. 44 | func (ft *FakeTime) Now() time.Time { return ft.Time } 45 | 46 | // Sleep adds the given duration to the global fake time. 47 | func (ft *FakeTime) Sleep(d time.Duration) { 48 | ft.Time = ft.Time.Add(d) 49 | } 50 | -------------------------------------------------------------------------------- /test-e2e/cip-auditor/entrypoint-from-container.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes 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 | 17 | # This script is meant to be used from inside the 18 | # gcr.io/k8s-testimages/kubekins-e2e image when running the promoter's e2e test 19 | # (e2e.go) as part of a Prow presubmit job. Because this script is so 20 | # bare-bones, we choose to just run it instead of creating a custom Docker image 21 | # that already has the setup logic in it. This way, we avoid having to 22 | # build/maintain a separate e2e environment image of our own. 23 | 24 | set -o errexit 25 | set -o nounset 26 | set -o pipefail 27 | set -o xtrace 28 | 29 | SCRIPT_ROOT=$(dirname "$(readlink -f "$0")") 30 | 31 | # Populate creds and turn on experimental docker features to support the "docker 32 | # manifest" subcommand. 33 | mkdir -p "${HOME}"/.docker 34 | cp -f "${SCRIPT_ROOT}/../../docker/config.json" "${HOME}/.docker" 35 | 36 | make -C "${SCRIPT_ROOT}/../.." test-e2e-cip-auditor 37 | -------------------------------------------------------------------------------- /test-e2e/cip/e2e-entrypoint-from-container.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes 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 | 17 | # This script is meant to be used from inside the 18 | # gcr.io/k8s-testimages/kubekins-e2e image when running the promoter's e2e test 19 | # (e2e.go) as part of a Prow presubmit job. Because this script is so 20 | # bare-bones, we choose to just run it instead of creating a custom Docker image 21 | # that already has the setup logic in it. This way, we avoid having to 22 | # build/maintain a separate e2e environment image of our own. 23 | 24 | set -o errexit 25 | set -o nounset 26 | set -o pipefail 27 | set -o xtrace 28 | 29 | SCRIPT_ROOT=$(dirname "$(readlink -f "$0")") 30 | 31 | # Populate creds and turn on experimental docker features to support the "docker 32 | # manifest" subcommand. 33 | mkdir -p "${HOME}"/.docker 34 | cp -f "${SCRIPT_ROOT}/../../docker/config.json" "${HOME}/.docker" 35 | 36 | # Invoke the e2e test! 37 | make -C "${SCRIPT_ROOT}/../.." test-e2e-cip 38 | -------------------------------------------------------------------------------- /internal/legacy/report/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 report 18 | 19 | import ( 20 | "io" 21 | 22 | "cloud.google.com/go/errorreporting" 23 | ) 24 | 25 | // Reporter requires a single method, called Report(), which corresponds to 26 | // calling Stackdriver Error Reporting for the real implementation. 27 | type Reporter interface { 28 | Report(errorreporting.Entry) 29 | } 30 | 31 | // ReportingFacility has a Reporter and Closer. Unlike LoggingFacility, there is 32 | // no need to have a struct type because the same thing is used to call Report() 33 | // and Close() on. This is because the real implementation calls both Report() 34 | // and Close() methods on the same errorreporting.Client type (and so, the fake 35 | // implementation follows suit and does the same). As such, there is no need to 36 | // have a wrapper struct around the two (separate) interfaces, and 37 | // ReportingFacility is just a plain interface. 38 | type ReportingFacility interface { 39 | Reporter 40 | io.Closer 41 | } 42 | -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | # Google Cloud Build configuration: https://cloud.google.com/cloud-build/docs/build-config 2 | # Image building process: https://git.k8s.io/test-infra/config/jobs/image-pushing/README.md 3 | 4 | # this must be specified in seconds. If omitted, defaults to 600s (10 mins) 5 | timeout: 1200s 6 | 7 | options: 8 | substitution_option: ALLOW_LOOSE 9 | machineType: E2_HIGHCPU_32 10 | 11 | steps: 12 | - name: 'gcr.io/cloud-builders/docker' 13 | entrypoint: make 14 | env: 15 | - GIT_TAG=$_GIT_TAG 16 | - PULL_BASE_REF=$_PULL_BASE_REF 17 | - CLOUDBUILD_REPO=$PROJECT_ID 18 | - IMG_VERSION=$_IMG_VERSION 19 | args: 20 | - image-push 21 | 22 | - name: 'gcr.io/gcp-runtimes/container-structure-test' 23 | id: structure-test 24 | args: 25 | - test 26 | - --image=gcr.io/$PROJECT_ID/kpromo:$_GIT_TAG 27 | - --config=container-structure.yaml 28 | 29 | substitutions: 30 | # _GIT_TAG will be filled with a git-based tag for the image, of the form 31 | # vYYYYMMDD-hash, and can be used as a substitution 32 | _GIT_TAG: '12345' 33 | _PULL_BASE_REF: 'dev' 34 | _IMG_VERSION: 'v4.1.0-0' 35 | 36 | tags: 37 | - 'kpromo' 38 | - ${_GIT_TAG} 39 | - ${_PULL_BASE_REF} 40 | - ${_IMG_VERSION} 41 | 42 | images: 43 | - 'gcr.io/$PROJECT_ID/kpromo:$_GIT_TAG' 44 | - 'gcr.io/$PROJECT_ID/kpromo:$_IMG_VERSION' 45 | - 'gcr.io/$PROJECT_ID/kpromo:latest' 46 | - 'gcr.io/$PROJECT_ID/kpromo:latest-canary' 47 | - 'gcr.io/$PROJECT_ID/kpromo-auditor:$_GIT_TAG' 48 | - 'gcr.io/$PROJECT_ID/kpromo-auditor:$_IMG_VERSION' 49 | - 'gcr.io/$PROJECT_ID/kpromo-auditor:latest' 50 | - 'gcr.io/$PROJECT_ID/kpromo-auditor:latest-canary' 51 | -------------------------------------------------------------------------------- /internal/legacy/report/fake.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 report 18 | 19 | import ( 20 | "bytes" 21 | "log" 22 | 23 | "cloud.google.com/go/errorreporting" 24 | ) 25 | 26 | // FakeReportingClient is a fake reporting client. 27 | type FakeReportingClient struct { 28 | reporter *log.Logger 29 | reportBuffer bytes.Buffer 30 | } 31 | 32 | // GetReportBuffer retrieves the reportBuffer. 33 | func (c *FakeReportingClient) GetReportBuffer() bytes.Buffer { 34 | return c.reportBuffer 35 | } 36 | 37 | // Report simply prints the entry to STDERR. Nothing goes over the network! 38 | func (c *FakeReportingClient) Report( 39 | e errorreporting.Entry, 40 | ) { 41 | c.reporter.Println(e) 42 | } 43 | 44 | // Close is a NOP (there is nothing to close). 45 | func (c *FakeReportingClient) Close() error { return nil } 46 | 47 | // NewFakeReportingClient creates a new FakeReportingClient that has the 48 | // reporter initialized to a basic logger to STDERR. 49 | func NewFakeReportingClient() *FakeReportingClient { 50 | c := FakeReportingClient{} 51 | c.reporter = log.New(&c.reportBuffer, "FAKE-REPORT", log.LstdFlags) 52 | 53 | return &c 54 | } 55 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/registry/set.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 registry 18 | 19 | import ( 20 | "sigs.k8s.io/promo-tools/v4/internal/legacy/container" 21 | "sigs.k8s.io/promo-tools/v4/types/image" 22 | ) 23 | 24 | // Various set manipulation operations. Some set operations are missing, 25 | // because, we don't use them. 26 | 27 | // ToTagSet converts a TagSlice to a TagSet. 28 | func (a TagSlice) ToTagSet() container.Set[image.Tag] { 29 | return container.NewSet(a...) 30 | } 31 | 32 | // Minus is a set operation. 33 | func (a TagSlice) Minus(b TagSlice) container.Set[image.Tag] { 34 | aSet := a.ToTagSet() 35 | bSet := b.ToTagSet() 36 | cSet := aSet.Minus(bSet) 37 | 38 | return cSet 39 | } 40 | 41 | // Union is a set operation. 42 | func (a TagSlice) Union(b TagSlice) container.Set[image.Tag] { 43 | aSet := a.ToTagSet() 44 | bSet := b.ToTagSet() 45 | cSet := aSet.Union(bSet) 46 | 47 | return cSet 48 | } 49 | 50 | // Intersection is a set operation. 51 | func (a TagSlice) Intersection(b TagSlice) container.Set[image.Tag] { 52 | aSet := a.ToTagSet() 53 | bSet := b.ToTagSet() 54 | cSet := aSet.Intersection(bSet) 55 | 56 | return cSet 57 | } 58 | -------------------------------------------------------------------------------- /test-e2e/README.md: -------------------------------------------------------------------------------- 1 | # E2E Testing Overview 2 | 3 | This directory has logic that allows the creation of deterministic Docker images 4 | using by loading golden archives for e2e testing. 5 | 6 | There are 2 flavors of e2e tests, each in its own subfolder: 7 | 8 | 1. cip: tests promotion logic (`e2e.go`) 9 | 2. cip-auditor: tests the auditing mechanism logic (`cip-auditor-e2e.go`) 10 | 11 | For both flavors, the testing binary uses test data in a `tests.yaml` to run the necessary tests. 12 | 13 | ## Running tests 14 | 15 | Here's a way to invoke the tests from your local checkout, against your own GCP test repository: 16 | 17 | ```console 18 | export CIP_E2E_KEY_FILE=path/to/secret/creds.json 19 | 20 | # For "cip" e2e tests. 21 | make test-e2e-cip 22 | 23 | # For "cip-auditor" e2e tests. 24 | make test-e2e-cip-auditor 25 | ``` 26 | 27 | ### cip (e2e.go) 28 | 29 | The test cases are defined in `./cip/tests.yaml`. Each test case has 2 parts: 30 | 31 | 1. an embedded promoter manifest 32 | 2. a before/after snapshot of GCRs to compare actual promoter runs with expected 33 | results 34 | 35 | In other words, for each test, `e2e.go` invokes the `cip` binary to perform a 36 | promotion run. After the promotion finishes, it checks expected GCR snapshots 37 | against the actual repositories (as defined in the embedded promoter manifest) 38 | to make sure that they do indeed match the expected snapshots as defined in the 39 | test case. 40 | 41 | ### cip-auditor (cip-auditor-e2e.go) 42 | 43 | The test cases are defined in `./cip-auditor/tests.yaml`. Each test case has 5 44 | parts 45 | 46 | 1. Clear GCP (test project) logs. 47 | 2. Set up preliminary GCR state. 48 | 3. Spin up the auditor on Cloud Run. 49 | 4. Modify GCR state (which should trigger the auditor to audit this change). 50 | 5. Check GCP (test project) logs on Stackdriver to check how the auditor 51 | behaved. 52 | -------------------------------------------------------------------------------- /go_with_version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2021 The Kubernetes 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 | 17 | # About: Stamp internal/version with logging information before either 18 | # building or running the provided go-code. 19 | # 20 | # Usage: 21 | # ./go_with_version.sh (build | run) path/to/code.go [args...] 22 | 23 | set -o errexit 24 | set -o nounset 25 | set -o pipefail 26 | 27 | printUsage() { 28 | >&2 echo "Usage: $0 (build | install | run ) path/to/code.go [args...]" 29 | } 30 | 31 | if [[ $# -lt 2 ]]; then 32 | >&2 echo "ERROR: Invalid number of arguments!" 33 | printUsage 34 | exit 1 35 | elif [ "$1" != build ] && [ "$1" != install ] && [ "$1" != run ]; then 36 | >&2 echo "ERROR: First argument was not 'build', 'install', or 'run'!" 37 | printUsage 38 | exit 1 39 | fi 40 | 41 | tool="$1" 42 | git_tree_state=dirty 43 | pkg=sigs.k8s.io/promo-tools/internal/version 44 | 45 | if git_status=$(git status --porcelain --untracked=no 2>/dev/null) && [[ -z "${git_status}" ]]; then 46 | git_tree_state=clean 47 | fi 48 | 49 | go "$1" -v -ldflags "-s -w \ 50 | -X $pkg.buildDate=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \ 51 | -X $pkg.gitCommit=$(git rev-parse HEAD 2>/dev/null || echo unknown) \ 52 | -X $pkg.gitTreeState=$git_tree_state \ 53 | -X $pkg.gitVersion=$(git describe --tags --abbrev=0 || echo unknown)" \ 54 | "${@:2}" 55 | 56 | echo "Finished running $tool" 57 | -------------------------------------------------------------------------------- /internal/legacy/container/set.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 container 18 | 19 | // Set is a basic set-like data structure. 20 | type Set[K comparable] map[K]struct{} 21 | 22 | // NewSet contructs a Set with the specified items. 23 | func NewSet[K comparable](items ...K) Set[K] { 24 | s := make(Set[K]) 25 | for _, item := range items { 26 | s[item] = struct{}{} 27 | } 28 | return s 29 | } 30 | 31 | // Insert inserts an item into the set. 32 | func (s Set[K]) Insert(item K) { 33 | s[item] = struct{}{} 34 | } 35 | 36 | // Minus returns a new set, by subtracting everything in b from a. 37 | func (a Set[K]) Minus(b Set[K]) Set[K] { 38 | c := make(Set[K]) 39 | for k, v := range a { 40 | c[k] = v 41 | } 42 | for k := range b { 43 | delete(c, k) 44 | } 45 | return c 46 | } 47 | 48 | // Union takes two sets and returns their union in a new set. 49 | func (a Set[K]) Union(b Set[K]) Set[K] { 50 | c := make(Set[K]) 51 | for k, v := range a { 52 | c[k] = v 53 | } 54 | for k, v := range b { 55 | c[k] = v 56 | } 57 | return c 58 | } 59 | 60 | // Intersection takes two sets and returns elements common to both. Note that we 61 | // throw away information about the values of the elements in b. 62 | func (a Set[K]) Intersection(b Set[K]) Set[K] { 63 | c := make(Set[K]) 64 | for k, v := range a { 65 | if _, ok := b[k]; ok { 66 | c[k] = v 67 | } 68 | } 69 | return c 70 | } 71 | -------------------------------------------------------------------------------- /internal/legacy/stream/subprocess.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 stream 18 | 19 | import ( 20 | "io" 21 | "os/exec" 22 | ) 23 | 24 | // Subprocess can spawn a subprocess and read from it. It can be used to read 25 | // from an io.Reader that produces JSON, or whatever else. 26 | type Subprocess struct { 27 | CmdInvocation []string 28 | cmd *exec.Cmd 29 | } 30 | 31 | // Produce runs the external process and returns two io.Readers (to stdout and 32 | // stderr). 33 | func (sp *Subprocess) Produce() (stdOut, stdErr io.Reader, err error) { 34 | invocation := sp.CmdInvocation 35 | cmd := exec.Command(invocation[0], invocation[1:]...) //nolint: gosec 36 | stdoutReader, err := cmd.StdoutPipe() 37 | if err != nil { 38 | return nil, nil, err 39 | } 40 | stderrReader, err := cmd.StderrPipe() 41 | if err != nil { 42 | return nil, nil, err 43 | } 44 | sp.cmd = cmd 45 | err = cmd.Start() 46 | if err != nil { 47 | return nil, nil, err 48 | } 49 | 50 | return stdoutReader, stderrReader, nil 51 | } 52 | 53 | // Close the subprocess by waiting for it. 54 | func (sp *Subprocess) Close() error { 55 | // The call to Wait() _cannot_ come before all reads from the pipe have been 56 | // completed. Otherwise, we may end up closing the pipe before the JSON 57 | // decoder has had time to read from it. 58 | // See https://golang.org/pkg/os/exec/#Cmd.StdoutPipe. 59 | return sp.cmd.Wait() 60 | } 61 | -------------------------------------------------------------------------------- /internal/legacy/stream/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 stream 18 | 19 | import ( 20 | "io" 21 | "time" 22 | 23 | "github.com/cenkalti/backoff/v4" 24 | ) 25 | 26 | // Producer is an interface for anything that can generate an io.Reader from 27 | // which we can read from (typically JSON output). 28 | type Producer interface { 29 | // The first two io.Readers are expected to be the stdout and stderr streams 30 | // from the process, respectively. 31 | Produce() (io.Reader, io.Reader, error) 32 | Close() error 33 | } 34 | 35 | // An ExternalRequest is anything that can create and then consume any stream. 36 | // The request comes bundled with something that can produce a stream 37 | // (io.Reader), and something that can read from that stream to populate some 38 | // arbitrary data structure. 39 | type ExternalRequest struct { 40 | RequestParams interface{} 41 | StreamProducer Producer 42 | } 43 | 44 | // BackoffDefault is the default Backoff behavior for network call retries. 45 | // 46 | // Previous values from k8s.io/apimachinery/pkg/util/wait.`Backoff`: 47 | // - Duration: time.Second 48 | // - Factor: 2 49 | // - Jitter: 0.1 50 | // - Steps: 45 51 | // - Cap: time.Second * 60. 52 | func BackoffDefault() *backoff.ExponentialBackOff { 53 | b := backoff.NewExponentialBackOff() 54 | b.InitialInterval = time.Second 55 | b.Multiplier = 2 56 | b.RandomizationFactor = 0.1 57 | b.MaxElapsedTime = time.Second * 60 58 | 59 | return b 60 | } 61 | -------------------------------------------------------------------------------- /promobot/readmanifest_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 promobot_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "sigs.k8s.io/yaml" 23 | 24 | "sigs.k8s.io/promo-tools/v4/promobot" 25 | ) 26 | 27 | func TestReadManifests(t *testing.T) { 28 | grid := []struct { 29 | Expected string 30 | Options promobot.PromoteFilesOptions 31 | }{ 32 | { 33 | Expected: "testdata/expected/onefile.yaml", 34 | Options: promobot.PromoteFilesOptions{ 35 | FilestoresPath: "testdata/manifests/onefile/filepromoter-manifest.yaml", 36 | FilesPath: "testdata/manifests/onefile/files.yaml", 37 | }, 38 | }, 39 | { 40 | Expected: "testdata/expected/manyfiles.yaml", 41 | Options: promobot.PromoteFilesOptions{ 42 | FilestoresPath: "testdata/manifests/manyfiles/filepromoter-manifest.yaml", 43 | FilesPath: "testdata/manifests/manyfiles/files/", 44 | }, 45 | }, 46 | { 47 | Expected: "testdata/expected/manyprojects.yaml", 48 | Options: promobot.PromoteFilesOptions{ 49 | ManifestsPath: "testdata/manifests/manyprojects/", 50 | }, 51 | }, 52 | } 53 | 54 | for _, g := range grid { 55 | t.Run(g.Expected, func(t *testing.T) { 56 | manifests, err := promobot.ReadManifests(g.Options) 57 | if err != nil { 58 | t.Fatalf("failed to read manifests: %v", err) 59 | } 60 | 61 | manifestYAML, err := yaml.Marshal(manifests) 62 | if err != nil { 63 | t.Fatalf("error serializing manifest: %v", err) 64 | } 65 | 66 | AssertMatchesFile(t, string(manifestYAML), g.Expected) 67 | }) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /promoter/image/ratelimit/roundtripper.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes 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 ratelimit 18 | 19 | import ( 20 | "context" 21 | "net/http" 22 | 23 | "golang.org/x/time/rate" 24 | ) 25 | 26 | const ( 27 | burst = 1 28 | 29 | // Number of events to pass to the crane rate limiter to match the 30 | // AR api limits: 31 | // https://github.com/kubernetes/registry.k8s.io/issues/153#issuecomment-1460913153 32 | // bentheelder: (83*60=4980) 33 | // Temp: We are temporarily dropping this from the max of 83 to 34 | // two thirds while the rate limiter is instrumented everywhere. 35 | MaxEvents = 50 36 | ) 37 | 38 | // RoundTripper wraps an http.RoundTripper with rate limiting. 39 | type RoundTripper struct { 40 | rateLimiter *rate.Limiter 41 | roundTripper http.RoundTripper 42 | } 43 | 44 | var _ http.RoundTripper = &RoundTripper{} 45 | 46 | var Limiter *RoundTripper 47 | 48 | func init() { 49 | if Limiter == nil { 50 | Limiter = NewRoundTripper(MaxEvents) 51 | } 52 | } 53 | 54 | func NewRoundTripper(limit rate.Limit) *RoundTripper { 55 | return &RoundTripper{ 56 | rateLimiter: rate.NewLimiter(limit, burst), 57 | roundTripper: http.DefaultTransport, 58 | } 59 | } 60 | 61 | func (rt *RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { 62 | // only rate limit read type calls, not writes 63 | if r.Method == http.MethodGet || r.Method == http.MethodHead { 64 | err := rt.rateLimiter.Wait(context.Background()) 65 | if err != nil { 66 | return nil, err 67 | } 68 | } 69 | 70 | return rt.roundTripper.RoundTrip(r) 71 | } 72 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | #### What type of PR is this? 11 | 12 | 27 | 28 | #### What this PR does / why we need it: 29 | 30 | #### Which issue(s) this PR fixes: 31 | 32 | 42 | 43 | #### Special notes for your reviewer: 44 | 45 | #### Does this PR introduce a user-facing change? 46 | 47 | 54 | 55 | ```release-note 56 | 57 | ``` 58 | -------------------------------------------------------------------------------- /internal/legacy/dockerregistry/schema/manifest_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kubernetes 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 schema 18 | 19 | import ( 20 | "os" 21 | "path/filepath" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/assert" 25 | "github.com/stretchr/testify/require" 26 | "sigs.k8s.io/release-utils/command" 27 | ) 28 | 29 | func TestParseThinManifestsFromDirPostsubmit(t *testing.T) { 30 | t.Setenv("JOB_TYPE", "postsubmit") 31 | 32 | tmpDir, err := os.MkdirTemp("", "k8s.io-") 33 | require.NoError(t, err) 34 | testDir := filepath.Join(tmpDir, "test") 35 | defer os.RemoveAll(tmpDir) 36 | 37 | const ( 38 | repo = "https://github.com/kubernetes/k8s.io" 39 | git = "git" 40 | commit = "86b8f390aac2e6c244868143ea03c8326c9064a0" 41 | ) 42 | 43 | require.NoError(t, command.New(git, "clone", repo, testDir).RunSilentSuccess()) 44 | require.NoError(t, command.NewWithWorkDir(testDir, git, "checkout", commit).RunSilentSuccess()) 45 | 46 | for _, onlyProwDiff := range []bool{true, false} { 47 | manifests, err := ParseThinManifestsFromDir( 48 | filepath.Join(testDir, "k8s.gcr.io"), onlyProwDiff, 49 | ) 50 | 51 | require.NoError(t, err) 52 | require.Len(t, manifests, 76) 53 | 54 | var digestCount, imageCount int 55 | for _, manifest := range manifests { 56 | imageCount += len(manifest.Images) 57 | for _, image := range manifest.Images { 58 | digestCount += len(image.Dmap) 59 | } 60 | } 61 | 62 | expectedDigestCount := 12344 63 | if onlyProwDiff { 64 | expectedDigestCount = 1 65 | } 66 | assert.Equal(t, expectedDigestCount, digestCount) 67 | assert.Equal(t, 623, imageCount) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /docker/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "credHelpers": { 3 | "gcr.io": "gcloud", 4 | "us.gcr.io": "gcloud", 5 | "eu.gcr.io": "gcloud", 6 | "asia.gcr.io": "gcloud", 7 | "staging-k8s.gcr.io": "gcloud", 8 | "marketplace.gcr.io": "gcloud", 9 | "africa-south1-docker.pkg.dev": "gcloud", 10 | "asia-east1-docker.pkg.dev": "gcloud", 11 | "asia-east2-docker.pkg.dev": "gcloud", 12 | "asia-northeast1-docker.pkg.dev": "gcloud", 13 | "asia-northeast2-docker.pkg.dev": "gcloud", 14 | "asia-northeast3-docker.pkg.dev": "gcloud", 15 | "asia-south1-docker.pkg.dev": "gcloud", 16 | "asia-south2-docker.pkg.dev": "gcloud", 17 | "asia-southeast1-docker.pkg.dev": "gcloud", 18 | "asia-southeast2-docker.pkg.dev": "gcloud", 19 | "australia-southeast1-docker.pkg.dev": "gcloud", 20 | "australia-southeast2-docker.pkg.dev": "gcloud", 21 | "europe-central2-docker.pkg.dev": "gcloud", 22 | "europe-north1-docker.pkg.dev": "gcloud", 23 | "europe-southwest1-docker.pkg.dev": "gcloud", 24 | "europe-west1-docker.pkg.dev": "gcloud", 25 | "europe-west10-docker.pkg.dev": "gcloud", 26 | "europe-west12-docker.pkg.dev": "gcloud", 27 | "europe-west2-docker.pkg.dev": "gcloud", 28 | "europe-west3-docker.pkg.dev": "gcloud", 29 | "europe-west4-docker.pkg.dev": "gcloud", 30 | "europe-west6-docker.pkg.dev": "gcloud", 31 | "europe-west8-docker.pkg.dev": "gcloud", 32 | "europe-west9-docker.pkg.dev": "gcloud", 33 | "me-central1-docker.pkg.dev": "gcloud", 34 | "me-central2-docker.pkg.dev": "gcloud", 35 | "northamerica-northeast1-docker.pkg.dev": "gcloud", 36 | "northamerica-northeast2-docker.pkg.dev": "gcloud", 37 | "southamerica-east1-docker.pkg.dev": "gcloud", 38 | "southamerica-west1-docker.pkg.dev": "gcloud", 39 | "us-central1-docker.pkg.dev": "gcloud", 40 | "us-east1-docker.pkg.dev": "gcloud", 41 | "us-east4-docker.pkg.dev": "gcloud", 42 | "us-east5-docker.pkg.dev": "gcloud", 43 | "us-south1-docker.pkg.dev": "gcloud", 44 | "us-west1-docker.pkg.dev": "gcloud", 45 | "us-west2-docker.pkg.dev": "gcloud", 46 | "us-west3-docker.pkg.dev": "gcloud", 47 | "us-west4-docker.pkg.dev": "gcloud" 48 | }, 49 | "experimental": "enabled" 50 | } 51 | -------------------------------------------------------------------------------- /hack/local-audit.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes 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 | 17 | # About: This script runs the auditor locally in --verbose mode. 18 | 19 | set -o errexit 20 | set -o nounset 21 | set -o pipefail 22 | 23 | REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P) 24 | 25 | # Print the usage for this script. 26 | usage() { 27 | echo >&2 "Usage: $0 " 28 | exit 1 29 | } 30 | 31 | # Program entrypoint. 32 | main() { 33 | # Ensure correct number of runtime params. 34 | if [[ $# != 1 ]]; then 35 | echo >&2 "Key-file not found." 36 | usage 37 | fi 38 | 39 | # Define the service account key-file. 40 | export GOOGLE_APPLICATION_CREDENTIALS=$1 41 | 42 | # Build CIP binary. 43 | pushd "${REPO_ROOT}" 44 | make build 45 | popd 46 | 47 | # Setup runtime arguments. 48 | local manifest_repo_url 49 | local manifest_repo_branch 50 | local manifest_repo_dir 51 | local gcp_project_id 52 | 53 | # Use default value if environment variable is not set. 54 | manifest_repo_url="${CIP_AUDIT_MANIFEST_REPO_URL:-https://github.com/kubernetes/k8s.io}" 55 | manifest_repo_branch="${CIP_AUDIT_MANIFEST_REPO_BRANCH:-main}" 56 | manifest_repo_dir="${CIP_AUDIT_MANIFEST_REPO_MANIFEST_DIR:-k8s.gcr.io}" 57 | gcp_project_id="${CIP_AUDIT_GCP_PROJECT_ID:-k8s-artifacts-prod}" 58 | 59 | # Start the auditor. 60 | "${REPO_ROOT}/cip" audit \ 61 | --branch="$manifest_repo_branch" \ 62 | --path="$manifest_repo_dir" \ 63 | --project="$gcp_project_id" \ 64 | --url="$manifest_repo_url" \ 65 | --verbose 66 | } 67 | 68 | main "$@" 69 | -------------------------------------------------------------------------------- /hack/verify-golangci-lint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes 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 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | VERSION=v2.6.1 22 | URL_BASE=https://raw.githubusercontent.com/golangci/golangci-lint 23 | URL=${URL_BASE}/${VERSION}/install.sh 24 | # If you update the version above you might need to update the checksum 25 | # if it does not match. We say might because in the past the install script 26 | # has been unchanged even if there is a new verion of golangci-lint. 27 | # To obtain the checksum, download the install script and run the following 28 | # command: 29 | # > sha256sum 30 | INSTALL_CHECKSUM=edfa587f31bde70db161d1e5b783e086a1627d7e2f7c91de5f7cca79bcdf8631 31 | 32 | if [[ ! -f .golangci.yml ]]; then 33 | echo 'ERROR: missing .golangci.yml in repo root' >&2 34 | exit 1 35 | fi 36 | 37 | if ! command -v golangci-lint; then 38 | INSTALL_SCRIPT=$(mktemp -d)/install.sh 39 | curl -sfL "${URL}" >"${INSTALL_SCRIPT}" 40 | # Cross-platform sha256sum/shasum check 41 | system=$(uname) 42 | if [[ "${system}" == "Darwin" ]]; then 43 | CHECK_CMD="echo \"${INSTALL_CHECKSUM} ${INSTALL_SCRIPT}\" | shasum -a 256 --check --status" 44 | else 45 | CHECK_CMD="echo \"${INSTALL_CHECKSUM} ${INSTALL_SCRIPT}\" | sha256sum --check --status" 46 | fi 47 | if eval "${CHECK_CMD}"; then 48 | chmod 755 "${INSTALL_SCRIPT}" 49 | ${INSTALL_SCRIPT} -b /tmp "${VERSION}" 50 | export PATH=${PATH}:/tmp 51 | pwd 52 | else 53 | echo 'ERROR: install script sha256 checksum invalid' >&2 54 | exit 1 55 | fi 56 | fi 57 | 58 | golangci-lint version 59 | golangci-lint linters 60 | golangci-lint run "$@" 61 | -------------------------------------------------------------------------------- /cmd/kpromo/cmd/cip/audit.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 cip 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/spf13/cobra" 23 | 24 | "sigs.k8s.io/promo-tools/v4/internal/legacy/cli" 25 | ) 26 | 27 | // auditCmd represents the base command when called without any subcommands 28 | // TODO: Update command description. 29 | var auditCmd = &cobra.Command{ 30 | Use: "audit", 31 | Short: "Run the image auditor", 32 | Long: `cip audit - Image auditor 33 | 34 | Start an audit server that responds to Pub/Sub push events. 35 | `, 36 | SilenceUsage: true, 37 | SilenceErrors: true, 38 | RunE: func(_ *cobra.Command, _ []string) error { 39 | if err := cli.RunAuditCmd(auditOpts); err != nil { 40 | return fmt.Errorf("run `cip audit`: %w", err) 41 | } 42 | return nil 43 | }, 44 | } 45 | 46 | var auditOpts = &cli.AuditOptions{} 47 | 48 | func init() { 49 | auditCmd.PersistentFlags().StringVar( 50 | &auditOpts.ProjectID, 51 | "project", 52 | "", 53 | "GCP project name (used for labeling error reporting logs in GCP)", 54 | ) 55 | 56 | auditCmd.PersistentFlags().StringVar( 57 | &auditOpts.RepoURL, 58 | "url", 59 | "", 60 | "repository URL for promoter manifests", 61 | ) 62 | 63 | auditCmd.PersistentFlags().StringVar( 64 | &auditOpts.RepoBranch, 65 | "branch", 66 | "", 67 | "git branch of the promoter manifest repo to checkout", 68 | ) 69 | 70 | auditCmd.PersistentFlags().StringVar( 71 | &auditOpts.ManifestPath, 72 | "path", 73 | "", 74 | "manifest path (relative to the root of promoter manifest repo)", 75 | ) 76 | 77 | auditCmd.PersistentFlags().BoolVar( 78 | &auditOpts.Verbose, 79 | "verbose", 80 | auditOpts.Verbose, 81 | "include extra logging information", 82 | ) 83 | 84 | CipCmd.AddCommand(auditCmd) 85 | } 86 | -------------------------------------------------------------------------------- /internal/legacy/audit/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 audit 18 | 19 | import ( 20 | reg "sigs.k8s.io/promo-tools/v4/internal/legacy/dockerregistry" 21 | "sigs.k8s.io/promo-tools/v4/internal/legacy/dockerregistry/registry" 22 | "sigs.k8s.io/promo-tools/v4/internal/legacy/logclient" 23 | "sigs.k8s.io/promo-tools/v4/internal/legacy/remotemanifest" 24 | "sigs.k8s.io/promo-tools/v4/internal/legacy/report" 25 | "sigs.k8s.io/promo-tools/v4/internal/legacy/stream" 26 | ) 27 | 28 | // GcrReadingFacility holds functions used to create streams for reading the 29 | // repository and manifest list. 30 | type GcrReadingFacility struct { 31 | ReadRepo func(*reg.SyncContext, registry.Context) stream.Producer 32 | ReadManifestList func(*reg.SyncContext, *reg.GCRManifestListContext) stream.Producer 33 | } 34 | 35 | // ServerContext holds all of the initialization data for the server to start 36 | // up. 37 | type ServerContext struct { 38 | ID string 39 | RemoteManifestFacility remotemanifest.Facility 40 | ErrorReportingFacility report.ReportingFacility 41 | LoggingFacility logclient.LoggingFacility 42 | GcrReadingFacility GcrReadingFacility 43 | } 44 | 45 | // PubSubMessageInner is the inner struct that holds the actual Pub/Sub 46 | // information. 47 | type PubSubMessageInner struct { 48 | Data []byte `json:"data,omitempty"` 49 | ID string `json:"id"` 50 | } 51 | 52 | // PubSubMessage is the payload of a Pub/Sub event. 53 | type PubSubMessage struct { 54 | Message PubSubMessageInner `json:"message"` 55 | Subscription string `json:"subscription"` 56 | } 57 | 58 | const ( 59 | // LogName is the auditing log name to use. This is the name that comes up 60 | // for "gcloud logging logs list". 61 | LogName = "cip-audit-log" 62 | ) 63 | -------------------------------------------------------------------------------- /hack/test-go.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2021 The Kubernetes 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 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P) 22 | 23 | # Default timeout is 1800s 24 | TEST_TIMEOUT=${TIMEOUT:-1800} 25 | 26 | # Write go test artifacts here 27 | ARTIFACTS=${ARTIFACTS:-"${REPO_ROOT}/tmp"} 28 | 29 | for arg in "$@" 30 | do 31 | case $arg in 32 | -t=*|--timeout=*) 33 | TEST_TIMEOUT="${arg#*=}" 34 | shift 35 | ;; 36 | -t|--timeout) 37 | TEST_TIMEOUT="$2" 38 | shift 39 | shift 40 | ;; 41 | --failfast) 42 | TEST_FAILFAST="-failfast" 43 | shift 44 | esac 45 | done 46 | 47 | cd "${REPO_ROOT}" 48 | 49 | mkdir -p "${ARTIFACTS}" 50 | 51 | go_test_flags=( 52 | -v 53 | -count=1 54 | -timeout="${TEST_TIMEOUT}s" 55 | -cover -coverprofile "${ARTIFACTS}/coverage.out" 56 | ${TEST_FAILFAST:-} 57 | ) 58 | 59 | packages=() 60 | mapfile -t packages < <(go list ./... | grep -v 'sigs.k8s.io/promo-tools/cmd\|test-e2e') 61 | 62 | export GO111MODULE=on 63 | 64 | # If the user rquests failing fast (so that they can iterate on the failing test 65 | # quickly without having to swim across the verbose log outputs from parallel 66 | # tests), then iterate through each module serially. 67 | if [[ -n "${TEST_FAILFAST:-}" ]]; then 68 | echo >&2 "Testing serially with -failfast" 69 | echo >&2 "${go_test_flags[@]}" 70 | for package in "${packages[@]}"; do 71 | go test "${go_test_flags[@]}" "${package}" 72 | done 73 | else 74 | # By default, we run tests in parallel. This is used in CI. 75 | go test "${go_test_flags[@]}" "${packages[@]}" 76 | fi 77 | 78 | go tool cover -html "${ARTIFACTS}/coverage.out" -o "${ARTIFACTS}/coverage.html" 79 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2021 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # About: This dockerfile builds the kpromo binary for auditor tests and production use. 16 | # 17 | # Usage: Since there are two variants to build, you must include the variant name during build-time. 18 | # kpromo production binary: 19 | # docker build --build-arg variant=prod /path/to/Dockerfile 20 | # test auditor: 21 | # docker build --build-arg variant=test /path/to/Dockerfile 22 | 23 | # Determine final build variant [prod | test]. 24 | ARG variant 25 | ARG GO_VERSION 26 | ARG OS_CODENAME 27 | FROM golang:1.25-trixie AS builder 28 | 29 | # Copy the sources 30 | WORKDIR /go/src/app 31 | COPY . ./ 32 | 33 | # Build 34 | ARG ARCH 35 | 36 | ENV CGO_ENABLED=0 37 | ENV GOOS=linux 38 | ENV GOARCH=${ARCH} 39 | 40 | RUN make kpromo 41 | 42 | FROM gcr.io/google.com/cloudsdktool/cloud-sdk:slim AS base 43 | 44 | WORKDIR / 45 | COPY --from=builder /go/src/app/bin/kpromo . 46 | 47 | # The Docker configuration file (which include credential helpers for 48 | # authenticating to various container registries) should be placed in the home 49 | # directory of the running user, so it can be detected by artifact promotion 50 | # tooling. 51 | COPY --from=builder /go/src/app/docker/config.json /root/.docker/config.json 52 | 53 | ENTRYPOINT ["/kpromo"] 54 | 55 | # Testing image 56 | FROM base AS test-variant 57 | 58 | # Include auditor testing fixtures. 59 | COPY --from=builder /go/src/app/test-e2e/cip-auditor/fixture /e2e-fixtures 60 | 61 | # Trigger the auditor on startup. 62 | ENTRYPOINT ["/kpromo", "cip", "audit", "--verbose"] 63 | 64 | # Production image 65 | FROM base AS prod-variant 66 | 67 | LABEL maintainers="Kubernetes Authors" 68 | LABEL description="kpromo: The Kubernetes project artifact promoter" 69 | 70 | # Allow the runtime argument to choose the final variant. 71 | FROM ${variant}-variant AS final 72 | -------------------------------------------------------------------------------- /dependencies.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | # Release version 3 | - name: "repo release version" 4 | version: 4.1.0 5 | refPaths: 6 | - path: VERSION 7 | 8 | # repo infra 9 | - name: "repo-infra" 10 | version: 0.2.2 11 | refPaths: 12 | - path: hack/verify-boilerplate.sh 13 | match: VERSION=v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))? 14 | 15 | # zeitgeist 16 | - name: "zeitgeist" 17 | version: 0.5.4 18 | refPaths: 19 | - path: hack/verify-dependencies.sh 20 | match: VERSION=v(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))? 21 | 22 | - name: "Debian: codename (default)" 23 | version: trixie 24 | refPaths: 25 | - path: Dockerfile 26 | match: FROM golang:\d+.\d+(alpha|beta|rc)?\.?(\d+)-(trixie) 27 | 28 | # golangci-lint 29 | - name: "golangci-lint" 30 | version: 2.6.1 31 | refPaths: 32 | - path: hack/verify-golangci-lint.sh 33 | match: VERSION=v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))? 34 | 35 | # Golang pre-releases are denoted as `1.y.z` 36 | # Example: go1.17rc1 37 | # 38 | # This entry is a stub of the major version to allow dependency checks to 39 | # pass when building Kubernetes using a pre-release of Golang. 40 | - name: "golang: 1." 41 | version: 1.25 42 | refPaths: 43 | - path: Dockerfile 44 | match: FROM golang:\d+.\d+(alpha|beta|rc)?\.?(\d+)-(trixie) 45 | 46 | - name: "golang: go.mod" 47 | version: 1.25 48 | refPaths: 49 | - path: go.mod 50 | match: go \d+.\d+ 51 | 52 | - name: "k8s.gcr.io/artifact-promoter/kpromo" 53 | version: v4.1.0-0 54 | refPaths: 55 | - path: cloudbuild.yaml 56 | match: "_IMG_VERSION: 'v((([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)-([0-9]+)'" 57 | - path: workspace_status.sh 58 | match: p_ IMG_VERSION v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)-([0-9]+) 59 | -------------------------------------------------------------------------------- /promobot/hash_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 promobot_test 18 | 19 | import ( 20 | "context" 21 | "os" 22 | "testing" 23 | 24 | "k8s.io/utils/diff" 25 | "sigs.k8s.io/yaml" 26 | 27 | "sigs.k8s.io/promo-tools/v4/promobot" 28 | ) 29 | 30 | func TestHash(t *testing.T) { 31 | ctx := context.Background() 32 | 33 | var opt promobot.GenerateManifestOptions 34 | opt.PopulateDefaults() 35 | 36 | opt.BaseDir = "testdata/files" 37 | 38 | manifest, err := promobot.GenerateManifest(ctx, opt) 39 | if err != nil { 40 | t.Fatalf("failed to generate manifest: %v", err) 41 | } 42 | 43 | manifestYAML, err := yaml.Marshal(manifest) 44 | if err != nil { 45 | t.Fatalf("error serializing manifest: %v", err) 46 | } 47 | 48 | AssertMatchesFile(t, string(manifestYAML), "testdata/files-manifest.yaml") 49 | } 50 | 51 | // AssertMatchesFile verifies that the contents of p match actual. 52 | // 53 | // We break this out into a file because we also support the 54 | // UPDATE_EXPECTED_OUTPUT magic env var. When that env var is 55 | // set, we will write the actual output to the expected file, which 56 | // is very handy when making bigger changes. The intention of these 57 | // tests is to make the changes explicit, particularly in code 58 | // review, not to force manual updates. 59 | func AssertMatchesFile(t *testing.T, actual, p string) { 60 | b, err := os.ReadFile(p) 61 | if err != nil { 62 | if os.Getenv("UPDATE_EXPECTED_OUTPUT") == "" { 63 | t.Fatalf("error reading file %q: %v", p, err) 64 | } 65 | } 66 | 67 | expected := string(b) 68 | 69 | if actual != expected { 70 | if os.Getenv("UPDATE_EXPECTED_OUTPUT") != "" { 71 | if err := os.WriteFile(p, []byte(actual), 0o644); err != nil { //nolint: gosec 72 | t.Fatalf("error writing file %q: %v", p, err) 73 | } 74 | } 75 | t.Errorf("actual did not match expected; diff=%s", diff.StringDiff(actual, expected)) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /internal/legacy/logclient/fake.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 logclient 18 | 19 | import ( 20 | "bytes" 21 | "log" 22 | ) 23 | 24 | // FakeLogClient is a fake log client. 25 | type FakeLogClient struct { 26 | // Because this is fake, there is no actual logging client. 27 | loggers [3]*log.Logger 28 | buffers [3]bytes.Buffer 29 | } 30 | 31 | // Close is a NOP (there is nothing to close). 32 | func (c *FakeLogClient) Close() error { return nil } 33 | 34 | // GetInfoLogger exposes the internal Info logger. 35 | func (c *FakeLogClient) GetInfoLogger() *log.Logger { 36 | return c.loggers[IndexLogInfo] 37 | } 38 | 39 | // GetErrorLogger exposes the internal Error logger. 40 | func (c *FakeLogClient) GetErrorLogger() *log.Logger { 41 | return c.loggers[IndexLogError] 42 | } 43 | 44 | // GetAlertLogger exposes the internal Alert logger. 45 | func (c *FakeLogClient) GetAlertLogger() *log.Logger { 46 | return c.loggers[IndexLogAlert] 47 | } 48 | 49 | // GetInfoBuffer exposes the internal Info logger's buffer. 50 | func (c *FakeLogClient) GetInfoBuffer() bytes.Buffer { 51 | return c.buffers[IndexLogInfo] 52 | } 53 | 54 | // GetErrorBuffer exposes the internal Error logger's buffer. 55 | func (c *FakeLogClient) GetErrorBuffer() bytes.Buffer { 56 | return c.buffers[IndexLogError] 57 | } 58 | 59 | // GetAlertBuffer exposes the internal Alert logger's buffer. 60 | func (c *FakeLogClient) GetAlertBuffer() bytes.Buffer { 61 | return c.buffers[IndexLogAlert] 62 | } 63 | 64 | // NewFakeLogClient returns a new FakeLogClient. 65 | func NewFakeLogClient() *FakeLogClient { 66 | c := FakeLogClient{} 67 | 68 | c.loggers[IndexLogInfo] = log. 69 | New(&c.buffers[IndexLogInfo], "FAKE-INFO", log.LstdFlags) 70 | c.loggers[IndexLogError] = log. 71 | New(&c.buffers[IndexLogError], "FAKE-ERROR", log.LstdFlags) 72 | c.loggers[IndexLogAlert] = log. 73 | New(&c.buffers[IndexLogAlert], "FAKE-ALERT", log.LstdFlags) 74 | 75 | return &c 76 | } 77 | -------------------------------------------------------------------------------- /internal/legacy/stream/http.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 stream 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "io" 23 | "net/http" 24 | "time" 25 | 26 | "github.com/sirupsen/logrus" 27 | 28 | "sigs.k8s.io/promo-tools/v4/promoter/image/ratelimit" 29 | ) 30 | 31 | // HTTP is a wrapper around the net/http's Request type. 32 | type HTTP struct { 33 | Req *http.Request 34 | Res *http.Response 35 | } 36 | 37 | const ( 38 | requestTimeoutSeconds = 3 39 | ) 40 | 41 | // Produce runs the external process and returns two io.Readers (to stdout and 42 | // stderr). In this case we equate the http.Respose "Body" with stdout. 43 | func (h *HTTP) Produce() (stdOut, stdErr io.Reader, err error) { 44 | client := http.Client{ 45 | Transport: ratelimit.Limiter, 46 | Timeout: time.Second * requestTimeoutSeconds, 47 | } 48 | 49 | // TODO: Does Close() need to be handled in a separate method? 50 | //nolint:bodyclose // we close the response body in Close(). 51 | h.Res, err = client.Do(h.Req) 52 | if err != nil { 53 | return nil, nil, err 54 | } 55 | 56 | if h.Res.StatusCode == http.StatusOK { 57 | return h.Res.Body, nil, nil 58 | } 59 | 60 | // Try to glean some additional information by reading from the response 61 | // body. 62 | buf := new(bytes.Buffer) 63 | _, err = buf.ReadFrom(h.Res.Body) 64 | if err != nil { 65 | logrus.Errorf("could not read from HTTP response body") 66 | return nil, nil, fmt.Errorf( 67 | "problems encountered: unexpected response code %d", 68 | h.Res.StatusCode, 69 | ) 70 | } 71 | 72 | return nil, nil, fmt.Errorf( 73 | "problems encountered: unexpected response code %d; body: %s", 74 | h.Res.StatusCode, 75 | buf.String(), 76 | ) 77 | } 78 | 79 | // Close closes the http request. This is required because otherwise there will 80 | // be a resource leak. 81 | // See https://stackoverflow.com/a/33238755/437583 82 | func (h *HTTP) Close() error { 83 | return h.Res.Body.Close() 84 | } 85 | -------------------------------------------------------------------------------- /internal/legacy/logclient/gcp.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 logclient 18 | 19 | import ( 20 | "context" 21 | "log" 22 | 23 | "cloud.google.com/go/logging" 24 | ) 25 | 26 | // GcpLogClient is a GCP log client. 27 | type GcpLogClient struct { 28 | logClient *logging.Client 29 | loggers [3]*log.Logger 30 | } 31 | 32 | // Close simply calls Close() to the underlying logging client (from which the 33 | // child loggers are derived). 34 | func (c *GcpLogClient) Close() error { 35 | return c.logClient.Close() 36 | } 37 | 38 | // GetInfoLogger exposes the internal Info logger. 39 | func (c *GcpLogClient) GetInfoLogger() *log.Logger { 40 | return c.loggers[IndexLogInfo] 41 | } 42 | 43 | // GetErrorLogger exposes the internal Error logger. 44 | func (c *GcpLogClient) GetErrorLogger() *log.Logger { 45 | return c.loggers[IndexLogError] 46 | } 47 | 48 | // GetAlertLogger exposes the internal Alert logger. 49 | func (c *GcpLogClient) GetAlertLogger() *log.Logger { 50 | return c.loggers[IndexLogAlert] 51 | } 52 | 53 | // NewGcpLogClient returns a new LoggingFacility that logs to GCP resources. As 54 | // such, it requires the GCP projectID as well as the logName to log to. 55 | func NewGcpLogClient( 56 | projectID, logName string, 57 | ) (*GcpLogClient, error) { 58 | c := GcpLogClient{} 59 | ctx := context.Background() 60 | 61 | // This creates a logging client that performs better logging than the 62 | // default behavior on GCP Stackdriver. For instance, logs sent with this 63 | // client are not split up over newlines, and also the severity levels are 64 | // actually understood by Stackdriver. 65 | logClient, err := logging.NewClient(ctx, projectID) 66 | if err != nil { 67 | return nil, err 68 | } 69 | c.logClient = logClient 70 | 71 | c.loggers[IndexLogInfo] = logClient. 72 | Logger(logName).StandardLogger(logging.Info) 73 | c.loggers[IndexLogError] = logClient. 74 | Logger(logName).StandardLogger(logging.Error) 75 | c.loggers[IndexLogAlert] = logClient. 76 | Logger(logName).StandardLogger(logging.Alert) 77 | 78 | return &c, nil 79 | } 80 | -------------------------------------------------------------------------------- /workspace_status.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes 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 | 17 | # Usage: workspace_status.sh [inject] 18 | 19 | set -o errexit 20 | set -o nounset 21 | set -o pipefail 22 | 23 | INJECTION=false 24 | 25 | # By specifying the injection argument, variables will separated by 26 | # their values with an '=', instead of a ' '. This allows for easier 27 | # variable injection when consuming this script in shell environments. 28 | if (( $# == 1 )) && [ "$1" == "inject" ]; then 29 | INJECTION=true 30 | fi 31 | 32 | #`p_` takes two arguments to define a workspace status variable: 33 | # 34 | # * the name of the variable 35 | # * a default value 36 | # 37 | # If an environment variable with the corresponding name is set, its value is 38 | # used. Otherwise, the provided default value is used. 39 | p_() { 40 | if (( $# == 2 )); then 41 | if [ "$INJECTION" = true ]; then 42 | echo "$1"=\""${!1:-$2}"\" 43 | else 44 | echo "$1" "${!1:-$2}" 45 | fi 46 | else 47 | return 1 48 | fi 49 | } 50 | 51 | build_date="$(date -u '+%Y%m%d')" 52 | 53 | # Create a placeholder git commit value if the git env cannot be found 54 | null_git_commit="no-git-env" 55 | git_commit="$(git describe --tags --always --dirty)" || \ 56 | git_commit="$null_git_commit" 57 | 58 | image_tag="v${build_date}-${git_commit}" 59 | if [[ ${git_commit} == "$null_git_commit" ]]; then 60 | image_tag="${GIT_TAG}" 61 | fi 62 | 63 | p_ GIT_COMMIT "${git_commit}" 64 | p_ IMG_REGISTRY gcr.io 65 | p_ IMG_REPOSITORY k8s-staging-artifact-promoter 66 | p_ IMG_NAME kpromo 67 | p_ IMG_TAG "${image_tag}" 68 | p_ IMG_VERSION v4.1.0-0 69 | p_ TEST_AUDIT_PROD_IMG_REPOSITORY us.gcr.io/k8s-gcr-audit-test-prod 70 | p_ TEST_AUDIT_STAGING_IMG_REPOSITORY gcr.io/k8s-gcr-audit-test-prod 71 | p_ TEST_AUDIT_PROJECT_ID k8s-gcr-audit-test-prod 72 | p_ TEST_AUDIT_PROJECT_NUMBER 375340694213 73 | p_ TEST_AUDIT_INVOKER_SERVICE_ACCOUNT k8s-infra-gcr-promoter@k8s-gcr-audit-test-prod.iam.gserviceaccount.com 74 | p_ TEST_STAGING_IMG_REPOSITORY gcr.io/k8s-staging-cip-test 75 | -------------------------------------------------------------------------------- /internal/version/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 version 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | "runtime" 23 | "strings" 24 | "text/tabwriter" 25 | ) 26 | 27 | var ( 28 | gitVersion string // semantic version, derived by build scripts 29 | gitCommit string // sha1 from git, output of $(git rev-parse HEAD) 30 | gitTreeState string // state of git tree, either "clean" or "dirty" 31 | buildDate string // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') 32 | ) 33 | 34 | type Info struct { 35 | GitVersion string `json:"gitVersion,omitempty"` 36 | GitCommit string `json:"gitCommit,omitempty"` 37 | GitTreeState string `json:"gitTreeState,omitempty"` 38 | BuildDate string `json:"buildDate,omitempty"` 39 | GoVersion string `json:"goVersion,omitempty"` 40 | Compiler string `json:"compiler,omitempty"` 41 | Platform string `json:"platform,omitempty"` 42 | } 43 | 44 | func Get() *Info { 45 | return &Info{ 46 | GitVersion: gitVersion, 47 | GitCommit: gitCommit, 48 | GitTreeState: gitTreeState, 49 | BuildDate: buildDate, 50 | GoVersion: runtime.Version(), 51 | Compiler: runtime.Compiler, 52 | Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), 53 | } 54 | } 55 | 56 | // String returns the string representation of the version info. 57 | func (i *Info) String() string { 58 | b := strings.Builder{} 59 | w := tabwriter.NewWriter(&b, 0, 0, 2, ' ', 0) 60 | 61 | fmt.Fprintf(w, "GitVersion:\t%s\n", i.GitVersion) 62 | fmt.Fprintf(w, "GitCommit:\t%s\n", i.GitCommit) 63 | fmt.Fprintf(w, "GitTreeState:\t%s\n", i.GitTreeState) 64 | fmt.Fprintf(w, "BuildDate:\t%s\n", i.BuildDate) 65 | fmt.Fprintf(w, "GoVersion:\t%s\n", i.GoVersion) 66 | fmt.Fprintf(w, "Compiler:\t%s\n", i.Compiler) 67 | fmt.Fprintf(w, "Platform:\t%s\n", i.Platform) 68 | 69 | w.Flush() 70 | return b.String() 71 | } 72 | 73 | // JSONString returns the JSON representation of the version info. 74 | func (i *Info) JSONString() (string, error) { 75 | b, err := json.MarshalIndent(i, "", " ") 76 | if err != nil { 77 | return "", err 78 | } 79 | return string(b), nil 80 | } 81 | -------------------------------------------------------------------------------- /api/files/manifest.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 files 18 | 19 | import ( 20 | "fmt" 21 | 22 | "sigs.k8s.io/yaml" 23 | ) 24 | 25 | const ( 26 | // S3Scheme is the scheme for URIs describing files on Amazon S3. 27 | S3Scheme = "s3" 28 | 29 | // GCSScheme is the scheme for URIs describing files on Google Cloud Storage (GCS). 30 | GCSScheme = "gs" 31 | ) 32 | 33 | //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate 34 | 35 | // Filestore holds information about a filestore (e.g. GCS or S3 bucket), 36 | // to be written in a manifest file. 37 | type Filestore struct { 38 | // Base is the leading part of an artifact path, including the scheme. 39 | // It is everything that is not the actual file name itself. 40 | // e.g. "gs://prod-artifacts/myproject" 41 | Base string `json:"base,omitempty"` 42 | ServiceAccount string `json:"service-account,omitempty"` 43 | Src bool `json:"src,omitempty"` 44 | } 45 | 46 | // File holds information about a file artifact. File artifacts are copied from 47 | // a source Filestore to N destination Filestores. 48 | type File struct { 49 | // Name is the relative path of the file, relative to the Filestore base 50 | Name string `json:"name"` 51 | // SHA256 holds the SHA256 hash of the specified file (hex encoded) 52 | SHA256 string `json:"sha256,omitempty"` 53 | } 54 | 55 | // Manifest stores the information in a manifest file (describing the 56 | // desired state of a Docker Registry). 57 | type Manifest struct { 58 | // Filestores contains the source and destination (Src/Dest) filestores. 59 | // Filestores are (for example) GCS or S3 buckets. 60 | // It is possible that in the future, we support promoting to multiple 61 | // filestores, in which case we would have more than just Src/Dest. 62 | Filestores []Filestore `json:"filestores,omitempty"` 63 | Files []File `json:"files,omitempty"` 64 | } 65 | 66 | // ParseManifest parses a Manifest. 67 | func ParseManifest(b []byte) (*Manifest, error) { 68 | m := &Manifest{} 69 | if err := yaml.Unmarshal(b, m); err != nil { 70 | return nil, fmt.Errorf("error parsing manifest: %v", err) 71 | } 72 | return m, nil 73 | } 74 | -------------------------------------------------------------------------------- /cmd/kpromo/cmd/run/files.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 run 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "github.com/spf13/cobra" 24 | 25 | "sigs.k8s.io/promo-tools/v4/promobot" 26 | ) 27 | 28 | // filesCmd represents the subcommand for `kpromo run files`. 29 | var filesCmd = &cobra.Command{ 30 | Use: "files", 31 | Short: "Promote files from a staging object store to production", 32 | SilenceUsage: true, 33 | SilenceErrors: true, 34 | RunE: func(_ *cobra.Command, _ []string) error { 35 | if err := runFilePromotion(filesOpts); err != nil { 36 | return fmt.Errorf("run `kpromo run files`: %w", err) 37 | } 38 | return nil 39 | }, 40 | } 41 | 42 | var filesOpts = &promobot.PromoteFilesOptions{} 43 | 44 | func init() { 45 | // TODO: Move this into a default options function in pkg/promobot 46 | filesOpts.PopulateDefaults() 47 | 48 | filesCmd.PersistentFlags().StringVar( 49 | &filesOpts.FilestoresPath, 50 | "filestores", 51 | filesOpts.FilestoresPath, 52 | "path to the `filestores` promoter manifest", 53 | ) 54 | 55 | filesCmd.PersistentFlags().StringVar( 56 | &filesOpts.FilesPath, 57 | "files", 58 | filesOpts.FilesPath, 59 | "path to the `files` manifest", 60 | ) 61 | 62 | filesCmd.PersistentFlags().StringVar( 63 | &filesOpts.ManifestsPath, 64 | "manifests", 65 | filesOpts.ManifestsPath, 66 | "path to manifests for multiple projects", 67 | ) 68 | 69 | // TODO: Consider moving this to the root command 70 | filesCmd.PersistentFlags().BoolVar( 71 | &filesOpts.Confirm, 72 | "confirm", 73 | filesOpts.Confirm, 74 | "initiate a PRODUCTION artifact promotion", 75 | ) 76 | 77 | filesCmd.PersistentFlags().BoolVar( 78 | &filesOpts.UseServiceAccount, 79 | "use-service-account", 80 | filesOpts.UseServiceAccount, 81 | "allow service account usage with gcloud and S3 calls", 82 | ) 83 | 84 | // TODO(kpromo): Consider marking manifest flags as required 85 | 86 | RunCmd.AddCommand(filesCmd) 87 | } 88 | 89 | func runFilePromotion(opts *promobot.PromoteFilesOptions) error { 90 | ctx := context.Background() 91 | 92 | return promobot.RunPromoteFiles(ctx, *opts) 93 | } 94 | 95 | // TODO: Validate options 96 | -------------------------------------------------------------------------------- /gh2gcs/gh2gcs.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 gh2gcs 18 | 19 | import ( 20 | "path/filepath" 21 | "strings" 22 | 23 | "github.com/sirupsen/logrus" 24 | "sigs.k8s.io/release-sdk/github" 25 | "sigs.k8s.io/release-sdk/object" 26 | ) 27 | 28 | // Config contains a slice of `ReleaseConfig` to be used when unmarshalling a 29 | // yaml config containing multiple repository configs. 30 | type Config struct { 31 | ReleaseConfigs []ReleaseConfig `yaml:"releaseConfigs"` 32 | } 33 | 34 | // ReleaseConfig contains source (GitHub) and destination (GCS) information 35 | // to perform a copy/upload operation. 36 | type ReleaseConfig struct { 37 | // GitHub options 38 | Org string `yaml:"org"` 39 | Repo string `yaml:"repo"` 40 | Tags []string `yaml:"tags"` 41 | IncludePrereleases bool `yaml:"includePrereleases"` 42 | 43 | // GCS options 44 | GCSBucket string `yaml:"gcsBucket"` 45 | ReleaseDir string `yaml:"releaseDir"` 46 | } 47 | 48 | // DownloadReleases downloads release assets to a local directory 49 | // Assets to download are derived from the tags specified in `ReleaseConfig`. 50 | func DownloadReleases(releaseCfg *ReleaseConfig, ghClient *github.GitHub, outputDir string) error { 51 | tags := releaseCfg.Tags 52 | tagsString := strings.Join(tags, ", ") 53 | 54 | logrus.Infof( 55 | "Downloading assets for the following %s/%s release tags: %s", 56 | releaseCfg.Org, 57 | releaseCfg.Repo, 58 | tagsString, 59 | ) 60 | 61 | return ghClient.DownloadReleaseAssets(releaseCfg.Org, releaseCfg.Repo, tags, outputDir) 62 | } 63 | 64 | // Upload copies a set of release assets from local directory to GCS 65 | // Assets to upload are derived from the tags specified in `ReleaseConfig`. 66 | func Upload(cfg *ReleaseConfig, _ *github.GitHub, outputDir string) error { 67 | uploadBase := filepath.Join(outputDir, cfg.Org, cfg.Repo) 68 | 69 | tags := cfg.Tags 70 | for _, tag := range tags { 71 | srcDir := filepath.Join(uploadBase, tag) 72 | gcsPath := filepath.Join(cfg.GCSBucket, cfg.ReleaseDir, tag) 73 | 74 | gcs := object.NewGCS() 75 | if err := gcs.CopyToRemote(srcDir, gcsPath); err != nil { 76 | return err 77 | } 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /hack/verify-archives.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes 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 | 17 | # About: 18 | # This script extracts the golden image tarballs and compares their contents against the 19 | # already-tracked golden folder. The purpose of this script is to verify that the tarballs 20 | # residing in golden-archives matches the original source of truth (golden). 21 | # 22 | # Usage: 23 | # verify-archives.sh repo-root 24 | # 25 | 26 | set -o errexit 27 | set -o nounset 28 | set -o pipefail 29 | 30 | repoRoot=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P) 31 | 32 | # Mapping for each golden image KEY=localPath VALUE=containerPath. 33 | declare -A paths 34 | paths[bar/1.0]=bar/1.0 35 | paths[foo/1.0-linux_amd64]=foo/linux_amd64/1.0-linux_amd64 36 | paths[foo/1.0-linux_s390x]=foo/linux_s390x/1.0-linux_s390x 37 | paths[foo/NOTAG-0]=foo/NOTAG/NOTAG-0 38 | 39 | # Setup mock directory structure. 40 | mockGolden=$(mktemp -d) 41 | mkdir "${mockGolden}/foo" 42 | mkdir "${mockGolden}/bar" 43 | 44 | goldenArchive="${repoRoot}/test-e2e/golden-images/archives/" 45 | goldenContent="${repoRoot}/test-e2e/golden-images/content" 46 | 47 | # Extracts data file from each container image. 48 | for path in "${!paths[@]}" 49 | do 50 | containerPath=${paths[$path]} 51 | # Get parent directory of file. 52 | goldenParent=${path%%/*} 53 | # Load image from tarball archive. 54 | output=$(docker load -i "${goldenArchive}${path}.tar") 55 | # Reformat docker load output to only grab the image name. 56 | if [[ "$output" =~ "Loaded image: "(.*) ]] 57 | then 58 | img="${BASH_REMATCH[1]}" 59 | echo $img 60 | else 61 | echo "Error: failed to get image name from docker load output" 62 | exit 1 63 | fi 64 | # Save container ID after creation. 65 | id=$(docker create "$img" /dev/null) 66 | # Specify the absolute path to the data file within the container. 67 | containerAbsPath="/golden/${containerPath}" 68 | # Copy data file from inside container to temporary directory. 69 | docker cp "$id":"$containerAbsPath" "$mockGolden/${goldenParent}" 70 | done 71 | 72 | # Ensure mock-golden is an exact copy of test-e2e/cip/golden. 73 | diff -r "$mockGolden" "$goldenContent" 74 | 75 | # Cleanup 76 | rm -rf "$mockGolden" 77 | -------------------------------------------------------------------------------- /cmd/kpromo/cmd/manifest/files.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 manifest 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "os" 23 | "path/filepath" 24 | 25 | "github.com/spf13/cobra" 26 | "sigs.k8s.io/yaml" 27 | 28 | "sigs.k8s.io/promo-tools/v4/promobot" 29 | ) 30 | 31 | // filesCmd represents the subcommand for `kpromo manifest files`. 32 | var filesCmd = &cobra.Command{ 33 | Use: "files", 34 | Short: "Promote files from a staging object store to production", 35 | SilenceUsage: true, 36 | SilenceErrors: true, 37 | RunE: func(_ *cobra.Command, _ []string) error { 38 | if err := runFileManifest(filesOpts); err != nil { 39 | return fmt.Errorf("run `kpromo manifest files`: %w", err) 40 | } 41 | return nil 42 | }, 43 | } 44 | 45 | var filesOpts = &promobot.GenerateManifestOptions{} 46 | 47 | func init() { 48 | // TODO: Move this into a default options function in pkg/promobot 49 | filesOpts.PopulateDefaults() 50 | 51 | filesCmd.PersistentFlags().StringVar( 52 | &filesOpts.BaseDir, 53 | "src", 54 | filesOpts.BaseDir, 55 | "the base directory to copy from", 56 | ) 57 | 58 | filesCmd.PersistentFlags().StringVar( 59 | &filesOpts.Prefix, 60 | "prefix", 61 | filesOpts.Prefix, 62 | "only export files starting with the provided prefix", 63 | ) 64 | 65 | // TODO: Consider moving this into a validation function 66 | //nolint:errcheck 67 | filesCmd.MarkPersistentFlagRequired("src") 68 | 69 | ManifestCmd.AddCommand(filesCmd) 70 | } 71 | 72 | func runFileManifest(opts *promobot.GenerateManifestOptions) error { 73 | ctx := context.Background() 74 | 75 | src, err := filepath.Abs(opts.BaseDir) 76 | if err != nil { 77 | return fmt.Errorf("resolving %q to absolute path: %w", src, err) 78 | } 79 | 80 | opts.BaseDir = src 81 | 82 | manifest, err := promobot.GenerateManifest(ctx, *opts) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | manifestYAML, err := yaml.Marshal(manifest) 88 | if err != nil { 89 | return fmt.Errorf("serializing manifest: %w", err) 90 | } 91 | 92 | if _, err := os.Stdout.Write(manifestYAML); err != nil { 93 | return err 94 | } 95 | 96 | return nil 97 | } 98 | 99 | // TODO: Validate options 100 | -------------------------------------------------------------------------------- /internal/legacy/cli/run.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 cli 18 | 19 | import ( 20 | "fmt" 21 | "sync" 22 | "time" 23 | 24 | "github.com/sirupsen/logrus" 25 | 26 | promoter "sigs.k8s.io/promo-tools/v4/promoter/image" 27 | options "sigs.k8s.io/promo-tools/v4/promoter/image/options" 28 | ) 29 | 30 | const ( 31 | // flags. 32 | PromoterManifestFlag = "manifest" 33 | PromoterThinManifestDirFlag = "thin-manifest-dir" 34 | PromoterSnapshotFlag = "snapshot" 35 | PromoterManifestBasedSnapshotOfFlag = "manifest-based-snapshot-of" 36 | PromoterOutputFlag = "output" 37 | ) 38 | 39 | type Hook struct { 40 | lastTime time.Time 41 | mu sync.RWMutex 42 | } 43 | 44 | func NewHook() *Hook { 45 | return &Hook{ 46 | lastTime: time.Now(), 47 | mu: sync.RWMutex{}, 48 | } 49 | } 50 | 51 | func (h *Hook) Fire(e *logrus.Entry) error { 52 | h.mu.Lock() 53 | e.Data["diff"] = e.Time.Sub(h.lastTime).Round(time.Millisecond) 54 | h.lastTime = e.Time 55 | h.mu.Unlock() 56 | return nil 57 | } 58 | 59 | func (h *Hook) Levels() []logrus.Level { 60 | return logrus.AllLevels 61 | } 62 | 63 | func RunPromoteCmd(opts *options.Options) error { 64 | cip := promoter.New(opts) 65 | 66 | logrus.SetFormatter(&logrus.TextFormatter{ 67 | DisableTimestamp: false, 68 | FullTimestamp: true, 69 | TimestampFormat: "15:04:05.000", 70 | }) 71 | logrus.AddHook(NewHook()) 72 | 73 | logrus.Infof("Options to check the Signatures: SignCheckIdentity: %s | SignCheckIdentityRegexp: %s | SignCheckIssuer: %s | SignCheckIssuerRegexp: %s", 74 | opts.SignCheckIdentity, opts.SignCheckIdentityRegexp, opts.SignCheckIssuer, opts.SignCheckIssuerRegexp, 75 | ) 76 | 77 | // Mode 1: Manifest list verification (removed) 78 | 79 | // Mode 2: Snapshots 80 | if opts.Snapshot != "" || opts.ManifestBasedSnapshotOf != "" { 81 | return cip.Snapshot(opts) 82 | } 83 | 84 | // Option summary applies to everything except snapshots 85 | // TODO: Implement if opts.JSONLogSummary { defer sc.LogJSONSummary() } 86 | 87 | // Mode 3: Security scan 88 | if opts.SeverityThreshold >= 0 { 89 | return cip.SecurityScan(opts) 90 | } 91 | 92 | // Mode 4: Image promotion 93 | if err := cip.PromoteImages(opts); err != nil { 94 | return fmt.Errorf("promote images: %w", err) 95 | } 96 | 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /promobot/hash.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 promobot 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "os" 23 | "path/filepath" 24 | "strings" 25 | 26 | "golang.org/x/xerrors" 27 | "sigs.k8s.io/release-utils/hash" 28 | 29 | api "sigs.k8s.io/promo-tools/v4/api/files" 30 | ) 31 | 32 | // GenerateManifestOptions holds the parameters for a hash-files operation. 33 | type GenerateManifestOptions struct { 34 | // BaseDir is the directory containing the files to hash 35 | BaseDir string 36 | 37 | // Prefix exports only files matching the specified prefix. 38 | // 39 | // If we were instead to change BaseDir, we would also 40 | // restrict the files, but the relative paths would also 41 | // change. 42 | Prefix string 43 | } 44 | 45 | // PopulateDefaults sets the default values for GenerateManifestOptions. 46 | func (o *GenerateManifestOptions) PopulateDefaults() { 47 | // There are no fields with non-empty default values 48 | // (but we still want to follow the PopulateDefaults pattern) 49 | } 50 | 51 | // GenerateManifest generates a manifest containing the files in options.BaseDir. 52 | func GenerateManifest(_ context.Context, options GenerateManifestOptions) (*api.Manifest, error) { 53 | manifest := &api.Manifest{} 54 | 55 | if options.BaseDir == "" { 56 | return nil, xerrors.New("must specify BaseDir") 57 | } 58 | 59 | basedir := options.BaseDir 60 | if !strings.HasSuffix(basedir, "/") { 61 | basedir += "/" 62 | } 63 | 64 | if err := filepath.Walk(basedir, func(p string, info os.FileInfo, err error) error { 65 | if err != nil { 66 | return err 67 | } 68 | if !strings.HasPrefix(p, basedir) { 69 | return fmt.Errorf("expected path %q to have prefix %q", p, basedir) 70 | } 71 | 72 | if !strings.HasPrefix(p, filepath.Join(basedir, options.Prefix)) { 73 | return nil 74 | } 75 | 76 | if !info.IsDir() { 77 | relativePath := strings.TrimPrefix(p, basedir) 78 | sha256, err := hash.SHA256ForFile(p) 79 | if err != nil { 80 | return fmt.Errorf("error hashing file %q: %w", p, err) 81 | } 82 | manifest.Files = append(manifest.Files, api.File{ 83 | Name: relativePath, 84 | SHA256: sha256, 85 | }) 86 | } 87 | return nil 88 | }); err != nil { 89 | return nil, fmt.Errorf("error walking path %q: %w", options.BaseDir, err) 90 | } 91 | 92 | return manifest, nil 93 | } 94 | -------------------------------------------------------------------------------- /cmd/kpromo/cmd/mm/mm.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 mm 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/spf13/cobra" 23 | 24 | "sigs.k8s.io/promo-tools/v4/image/manifest" 25 | ) 26 | 27 | // TODO(cip-mm): Remove in the next minor release. 28 | 29 | var MMCmd = &cobra.Command{ 30 | Short: "[DEPRECATED] mm → Manifest Modifier", 31 | Long: `[DEPRECATED] mm → Manifest Modifier 32 | 33 | This tool **m**odifies promoter **m**anifests. For now it dumps some filtered 34 | subset of a staging GCR and merges those contents back into a given promoter 35 | manifest.`, 36 | Use: "mm", 37 | SilenceUsage: true, 38 | SilenceErrors: true, 39 | RunE: func(_ *cobra.Command, _ []string) error { 40 | return run() 41 | }, 42 | } 43 | 44 | type modifyOptions struct { 45 | baseDir string 46 | stagingRepo string 47 | filterImage string 48 | filterDigest string 49 | filterTag string 50 | } 51 | 52 | var modifyOpts = &modifyOptions{} 53 | 54 | func init() { 55 | MMCmd.PersistentFlags().StringVar( 56 | &modifyOpts.baseDir, 57 | "base_dir", 58 | "", 59 | "the manifest directory to look at and modify", 60 | ) 61 | MMCmd.PersistentFlags().StringVar( 62 | &modifyOpts.stagingRepo, 63 | "staging_repo", 64 | "", 65 | "the staging repo which we want to read from", 66 | ) 67 | MMCmd.PersistentFlags().StringVar( 68 | &modifyOpts.filterImage, 69 | "filter_image", 70 | "", 71 | "filter staging repo by this image name", 72 | ) 73 | MMCmd.PersistentFlags().StringVar( 74 | &modifyOpts.filterDigest, 75 | "filter_digest", 76 | "", 77 | "filter images by this digest", 78 | ) 79 | MMCmd.PersistentFlags().StringVar( 80 | &modifyOpts.filterTag, 81 | "filter_tag", 82 | "", 83 | "filter images by this tag", 84 | ) 85 | } 86 | 87 | func run() error { 88 | opt := manifest.GrowOptions{} 89 | if err := opt.Populate( 90 | modifyOpts.baseDir, 91 | modifyOpts.stagingRepo, 92 | []string{modifyOpts.filterImage}, 93 | []string{modifyOpts.filterDigest}, 94 | []string{modifyOpts.filterTag}, 95 | ); err != nil { 96 | return err 97 | } 98 | 99 | if err := opt.Validate(); err != nil { 100 | return err 101 | } 102 | 103 | ctx := context.Background() 104 | return manifest.Grow(ctx, &opt) 105 | } 106 | -------------------------------------------------------------------------------- /promoter/file/manifest.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 file 18 | 19 | import ( 20 | "context" 21 | "errors" 22 | "fmt" 23 | 24 | "github.com/sirupsen/logrus" 25 | 26 | api "sigs.k8s.io/promo-tools/v4/api/files" 27 | ) 28 | 29 | // ManifestPromoter promotes files as described in Manifest. 30 | type ManifestPromoter struct { 31 | Manifest *api.Manifest 32 | 33 | // Confirm, if set, will trigger a PRODUCTION artifact promotion. 34 | Confirm bool 35 | 36 | // UseServiceAccount must be true, for service accounts to be used 37 | // This gives some protection against a hostile manifest. 38 | UseServiceAccount bool 39 | } 40 | 41 | // BuildOperations builds the required operations to sync from the 42 | // Source Filestore to all Dest Filestores in the manifest. 43 | func (p *ManifestPromoter) BuildOperations( 44 | ctx context.Context, 45 | ) ([]SyncFileOp, error) { 46 | source, err := getSourceFilestore(p.Manifest) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | var operations []SyncFileOp 52 | 53 | for i := range p.Manifest.Filestores { 54 | filestore := &p.Manifest.Filestores[i] 55 | if filestore.Src { 56 | continue 57 | } 58 | logrus.Infof("processing destination %q", filestore.Base) 59 | fp := &FilestorePromoter{ 60 | Source: source, 61 | Dest: filestore, 62 | Files: p.Manifest.Files, 63 | Confirm: p.Confirm, 64 | UseServiceAccount: p.UseServiceAccount, 65 | } 66 | ops, err := fp.BuildOperations(ctx) 67 | if err != nil { 68 | return nil, fmt.Errorf( 69 | "error building promotion operations for %q: %v", 70 | filestore.Base, err) 71 | } 72 | operations = append(operations, ops...) 73 | } 74 | 75 | return operations, nil 76 | } 77 | 78 | // getSourceFilestore returns the Filestore with the source attribute 79 | // It returns an error if the source filestore cannot be found, or if 80 | // multiple filestores are marked as the source. 81 | func getSourceFilestore(manifest *api.Manifest) (*api.Filestore, error) { 82 | var source *api.Filestore 83 | for i := range manifest.Filestores { 84 | filestore := &manifest.Filestores[i] 85 | if filestore.Src { 86 | if source != nil { 87 | return nil, errors.New("found multiple source filestores") 88 | } 89 | source = filestore 90 | } 91 | } 92 | if source == nil { 93 | return nil, errors.New("source filestore not found") 94 | } 95 | return source, nil 96 | } 97 | -------------------------------------------------------------------------------- /promoter/file/filestore_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes 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 file 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/require" 23 | 24 | api "sigs.k8s.io/promo-tools/v4/api/files" 25 | ) 26 | 27 | func Test_useStorageClientAuth(t *testing.T) { 28 | type args struct { 29 | filestore *api.Filestore 30 | useServiceAccount bool 31 | confirm bool 32 | } 33 | tests := []struct { 34 | name string 35 | args args 36 | want bool 37 | wantErr bool 38 | }{ 39 | { 40 | name: "production", 41 | args: args{ 42 | filestore: &api.Filestore{ 43 | ServiceAccount: "good@service.account", 44 | }, 45 | confirm: true, 46 | }, 47 | want: true, 48 | wantErr: false, 49 | }, 50 | { 51 | name: "production without service account", 52 | args: args{ 53 | filestore: &api.Filestore{}, 54 | confirm: true, 55 | }, 56 | want: false, 57 | wantErr: true, 58 | }, 59 | { 60 | name: "production source filestore without service account", 61 | args: args{ 62 | filestore: &api.Filestore{ 63 | Src: true, 64 | }, 65 | confirm: true, 66 | }, 67 | want: false, 68 | wantErr: false, 69 | }, 70 | { 71 | name: "non-production", 72 | args: args{ 73 | filestore: &api.Filestore{}, 74 | confirm: false, 75 | }, 76 | want: false, 77 | wantErr: false, 78 | }, 79 | { 80 | name: "non-production with service account failure", 81 | args: args{ 82 | filestore: &api.Filestore{}, 83 | confirm: false, 84 | useServiceAccount: true, 85 | }, 86 | want: false, 87 | wantErr: true, 88 | }, 89 | { 90 | name: "non-production with service account success", 91 | args: args{ 92 | filestore: &api.Filestore{ 93 | ServiceAccount: "good@service.account", 94 | }, 95 | confirm: false, 96 | useServiceAccount: true, 97 | }, 98 | want: true, 99 | wantErr: false, 100 | }, 101 | } 102 | for _, tt := range tests { 103 | t.Run(tt.name, func(t *testing.T) { 104 | got, err := useStorageClientAuth( 105 | tt.args.filestore, 106 | tt.args.useServiceAccount, 107 | tt.args.confirm, 108 | ) 109 | 110 | if tt.wantErr { 111 | require.Error(t, err) 112 | } 113 | 114 | require.Equal(t, tt.want, got) 115 | }, 116 | ) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /cmd/kpromo/cmd/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 cmd 18 | 19 | import ( 20 | "github.com/sirupsen/logrus" 21 | "github.com/spf13/cobra" 22 | "sigs.k8s.io/release-utils/log" 23 | "sigs.k8s.io/release-utils/version" 24 | 25 | "sigs.k8s.io/promo-tools/v4/cmd/kpromo/cmd/cip" 26 | "sigs.k8s.io/promo-tools/v4/cmd/kpromo/cmd/gh" 27 | "sigs.k8s.io/promo-tools/v4/cmd/kpromo/cmd/manifest" 28 | "sigs.k8s.io/promo-tools/v4/cmd/kpromo/cmd/mm" 29 | "sigs.k8s.io/promo-tools/v4/cmd/kpromo/cmd/pr" 30 | "sigs.k8s.io/promo-tools/v4/cmd/kpromo/cmd/run" 31 | "sigs.k8s.io/promo-tools/v4/cmd/kpromo/cmd/sigcheck" 32 | ) 33 | 34 | // rootCmd represents the base command when called without any subcommands 35 | // TODO: Update command description. 36 | var rootCmd = &cobra.Command{ 37 | Use: "kpromo", 38 | Short: "Kubernetes project artifact promoter", 39 | Long: `kpromo - Kubernetes project artifact promoter 40 | 41 | kpromo is a tool responsible for artifact promotion. 42 | 43 | It has two operation modes: 44 | - "run" - Execute a file promotion (formerly "promobot-files") (image promotion coming soon) 45 | - "manifest" - Generate/modify a file manifest to target for promotion (image support coming soon) 46 | 47 | Expectations: 48 | - "kpromo run" should only be run in auditable environments 49 | - "kpromo manifest" should primarily be run by contributors 50 | 51 | Each subcommand should contain its own self-describing help output which 52 | clarifies its purpose.`, 53 | PersistentPreRunE: initLogging, 54 | } 55 | 56 | type rootOptions struct { 57 | logLevel string 58 | } 59 | 60 | var rootOpts = &rootOptions{} 61 | 62 | // Execute adds all child commands to the root command and sets flags appropriately. 63 | // This is called by main.main(). It only needs to happen once to the rootCmd. 64 | func Execute() { 65 | if err := rootCmd.Execute(); err != nil { 66 | logrus.Fatal(err) 67 | } 68 | } 69 | 70 | func init() { 71 | rootCmd.PersistentFlags().StringVar( 72 | &rootOpts.logLevel, 73 | "log-level", 74 | "info", 75 | "the logging verbosity, either "+log.LevelNames(), 76 | ) 77 | 78 | rootCmd.AddCommand(run.RunCmd) 79 | rootCmd.AddCommand(cip.CipCmd) 80 | rootCmd.AddCommand(gh.GHCmd) 81 | rootCmd.AddCommand(manifest.ManifestCmd) 82 | rootCmd.AddCommand(pr.PRCmd) 83 | // TODO(cip-mm): Remove in the next minor release. 84 | rootCmd.AddCommand(mm.MMCmd) 85 | rootCmd.AddCommand(version.Version()) 86 | sigcheck.Add(rootCmd) 87 | } 88 | 89 | func initLogging(*cobra.Command, []string) error { 90 | return log.SetupGlobalLogger(rootOpts.logLevel) 91 | } 92 | -------------------------------------------------------------------------------- /internal/legacy/signals/signals.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes 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 signals 18 | 19 | import ( 20 | "os" 21 | "os/signal" 22 | "syscall" 23 | 24 | "github.com/sirupsen/logrus" 25 | ) 26 | 27 | var ( 28 | // ExitSignals are used to determine if an incoming os.Signal should cause termination. 29 | ExitSignals = map[os.Signal]bool{ 30 | syscall.SIGHUP: true, 31 | syscall.SIGINT: true, 32 | syscall.SIGABRT: true, 33 | syscall.SIGILL: true, 34 | syscall.SIGQUIT: true, 35 | syscall.SIGTERM: true, 36 | syscall.SIGSEGV: true, 37 | syscall.SIGTSTP: true, 38 | } 39 | // ExitChannel is for gracefully terminating the LogSignals() function. 40 | ExitChannel = make(chan bool, 1) 41 | // SignalChannel is for listening to OS signals. 42 | SignalChannel = make(chan os.Signal, 1) 43 | // Debug is defined globally for mocking logrus.Debug statements. 44 | Debug func(args ...interface{}) = logrus.Debug 45 | ) 46 | 47 | // Watch concurrently logs debug statements when encountering interrupt signals from the OS. 48 | // This function relies on the os/signal package which may not capture SIGKILL and SIGSTOP. 49 | func Watch() { 50 | Debug("Watching for OS Signals...") 51 | // Observe all signals, excluding SIGKILL and SIGSTOP. 52 | signal.Notify(SignalChannel) 53 | // Continuously log signals. 54 | go LogSignals() 55 | } 56 | 57 | // Stop gracefully terminates the concurrent signal logging. 58 | // TODO: @tylerferrara Currently we don't gracefully exit, since the Auditor is designed 59 | // to run indefinitely. In the future, we should enable graceful termination to allow 60 | // this to be called from a Shutdown() function. 61 | func Stop() { 62 | ExitChannel <- true 63 | } 64 | 65 | // LogSignals continuously prints logging statements for each signal it observes, 66 | // exiting only when observing an exit signal. 67 | func LogSignals() { 68 | for { 69 | select { 70 | case sig := <-SignalChannel: 71 | LogSignal(sig) 72 | case <-ExitChannel: 73 | // Gracefully terminate. 74 | return 75 | } 76 | } 77 | } 78 | 79 | // LogSignal prints a logging statements for the given signal, 80 | // exiting only when observing an exit signal. 81 | func LogSignal(sig os.Signal) { 82 | Debug("Encoutered signal: ", sig.String()) 83 | // Handle exit signals. 84 | if _, found := ExitSignals[sig]; found { 85 | Debug("Exiting from signal: ", sig.String()) 86 | // If we get here, an exit signal was seen. We must handle this by forcing 87 | // the program to exit. Without this, the program would ignore all exit signals 88 | // except SIGKILL. 89 | Stop() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /hack/init-buildx.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2021 The Kubernetes 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 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | export DOCKER_CLI_EXPERIMENTAL=enabled 22 | 23 | # Expected builder output 24 | # 25 | # Name: kpromo-multiarch 26 | # Driver: docker-container 27 | # 28 | # Nodes: 29 | # Name: kpromo-multiarch0 30 | # Endpoint: unix:///var/run/docker.sock 31 | # Status: running 32 | # Platforms: linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6 33 | current_builder="$(docker buildx inspect)" 34 | 35 | # We can skip setup if the current builder already has multi-arch 36 | # AND if it isn't the "docker" driver, which doesn't work 37 | # 38 | # From https://docs.docker.com/buildx/working-with-buildx/#build-with-buildx: 39 | # "You can run Buildx in different configurations that are exposed through a 40 | # driver concept. Currently, Docker supports a “docker” driver that uses the 41 | # BuildKit library bundled into the docker daemon binary, and a 42 | # “docker-container” driver that automatically launches BuildKit inside a 43 | # Docker container. 44 | # 45 | # The user experience of using Buildx is very similar across drivers. 46 | # However, there are some features that are not currently supported by the 47 | # “docker” driver, because the BuildKit library which is bundled into docker 48 | # daemon uses a different storage component. In contrast, all images built with 49 | # the “docker” driver are automatically added to the “docker images” view by 50 | # default, whereas when using other drivers, the method for outputting an image 51 | # needs to be selected with --output." 52 | if ! grep -q "^Driver: docker$" <<<"${current_builder}" \ 53 | && grep -q "linux/amd64" <<<"${current_builder}" \ 54 | && grep -q "linux/arm" <<<"${current_builder}" \ 55 | && grep -q "linux/arm64" <<<"${current_builder}" \ 56 | && grep -q "linux/ppc64le" <<<"${current_builder}" \ 57 | && grep -q "linux/s390x" <<<"${current_builder}"; then 58 | exit 0 59 | fi 60 | 61 | # Ensure qemu is in binfmt_misc 62 | # NOTE: Please always pin this to a digest for predictability/auditability 63 | # Last updated: 08/21/2020 64 | if [ "$(uname)" == 'Linux' ]; then 65 | docker run --rm --privileged multiarch/qemu-user-static@sha256:c772ee1965aa0be9915ee1b018a0dd92ea361b4fa1bcab5bbc033517749b2af4 --reset -p yes 66 | fi 67 | 68 | # Ensure we use a builder that can leverage it (the default on linux will not) 69 | docker buildx rm kpromo-multiarch || true 70 | docker buildx create --use --name=kpromo-multiarch 71 | docker buildx inspect --bootstrap 72 | -------------------------------------------------------------------------------- /api/files/validation.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 files 18 | 19 | import ( 20 | "encoding/hex" 21 | "errors" 22 | "fmt" 23 | "strings" 24 | ) 25 | 26 | const ( 27 | Backslash = "://" 28 | ) 29 | 30 | // Validate checks for semantic errors in the yaml fields (the structure of the 31 | // yaml is checked during unmarshaling). 32 | func (m *Manifest) Validate() error { 33 | if err := ValidateFilestores(m.Filestores); err != nil { 34 | return err 35 | } 36 | 37 | return ValidateFiles(m.Files) 38 | } 39 | 40 | // ValidateFilestores validates the Filestores field of the manifest. 41 | func ValidateFilestores(filestores []Filestore) error { 42 | if len(filestores) == 0 { 43 | return errors.New("at least one filestore must be specified") 44 | } 45 | 46 | var source *Filestore 47 | destinationCount := 0 48 | 49 | for i := range filestores { 50 | filestore := &filestores[i] 51 | 52 | if filestore.Base == "" { 53 | return errors.New("filestore did not have base set") 54 | } 55 | 56 | // Currently we support GCS and s3 backends. 57 | switch { 58 | case strings.HasPrefix(filestore.Base, GCSScheme+Backslash): 59 | // ok 60 | case strings.HasPrefix(filestore.Base, S3Scheme+Backslash): 61 | // ok 62 | default: 63 | return fmt.Errorf( 64 | "filestore has unsupported scheme in base %q", 65 | filestore.Base) 66 | } 67 | 68 | if filestore.Src { 69 | if source != nil { 70 | return errors.New("found multiple source filestores") 71 | } 72 | source = filestore 73 | } else { 74 | destinationCount++ 75 | } 76 | } 77 | if source == nil { 78 | return errors.New("source filestore not found") 79 | } 80 | 81 | if destinationCount == 0 { 82 | return errors.New("no destination filestores found") 83 | } 84 | 85 | return nil 86 | } 87 | 88 | // ValidateFiles validates the Files field of the manifest. 89 | func ValidateFiles(files []File) error { 90 | if len(files) == 0 { 91 | return errors.New("at least one file must be specified") 92 | } 93 | 94 | for i := range files { 95 | f := &files[i] 96 | 97 | if f.Name == "" { 98 | return errors.New("name is required for file") 99 | } 100 | 101 | if f.SHA256 == "" { 102 | return errors.New("sha256 is required for file") 103 | } 104 | 105 | sha256, err := hex.DecodeString(f.SHA256) 106 | if err != nil { 107 | return fmt.Errorf("sha256 was not valid (not hex): %q", f.SHA256) 108 | } 109 | 110 | if len(sha256) != 32 { 111 | return fmt.Errorf("sha256 was not valid (bad length): %q", f.SHA256) 112 | } 113 | } 114 | 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /internal/legacy/cli/audit.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes 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 cli 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | guuid "github.com/google/uuid" 24 | "github.com/sirupsen/logrus" 25 | 26 | "sigs.k8s.io/promo-tools/v4/internal/legacy/audit" 27 | "sigs.k8s.io/promo-tools/v4/internal/legacy/reqcounter" 28 | "sigs.k8s.io/promo-tools/v4/internal/legacy/signals" 29 | ) 30 | 31 | type AuditOptions struct { 32 | ProjectID string 33 | RepoURL string 34 | RepoBranch string 35 | ManifestPath string 36 | UUID string 37 | Verbose bool 38 | } 39 | 40 | func RunAuditCmd(opts *AuditOptions) error { 41 | opts.set() 42 | 43 | if err := validateAuditOptions(opts); err != nil { 44 | return fmt.Errorf("validating audit options: %w", err) 45 | } 46 | 47 | auditorContext, err := audit.InitRealServerContext( 48 | opts.ProjectID, 49 | opts.RepoURL, 50 | opts.RepoBranch, 51 | opts.ManifestPath, 52 | opts.UUID, 53 | ) 54 | if err != nil { 55 | return fmt.Errorf("creating auditor context: %w", err) 56 | } 57 | 58 | if opts.Verbose { 59 | // Enable verbose logging. 60 | logrus.SetLevel(logrus.DebugLevel) 61 | // Initialize global counter to track the number of HTTP requests made to GCR. 62 | reqcounter.Init() 63 | // Watch for OS signals. 64 | signals.Watch() 65 | } 66 | 67 | auditorContext.RunAuditor() 68 | 69 | return nil 70 | } 71 | 72 | func (o *AuditOptions) set() { 73 | logrus.Infof("Setting image auditor options...") 74 | 75 | if o.ProjectID == "" { 76 | o.ProjectID = os.Getenv("CIP_AUDIT_GCP_PROJECT_ID") 77 | } 78 | 79 | if o.RepoURL == "" { 80 | o.RepoURL = os.Getenv("CIP_AUDIT_MANIFEST_REPO_URL") 81 | } 82 | 83 | if o.RepoBranch == "" { 84 | o.RepoBranch = os.Getenv("CIP_AUDIT_MANIFEST_REPO_BRANCH") 85 | } 86 | 87 | if o.ManifestPath == "" { 88 | o.ManifestPath = os.Getenv("CIP_AUDIT_MANIFEST_REPO_MANIFEST_DIR") 89 | } 90 | 91 | // TODO: Should we allow this to be configurable via the command line? 92 | o.UUID = os.Getenv("CIP_AUDIT_TESTCASE_UUID") 93 | if o.UUID != "" { 94 | logrus.Infof("Starting auditor in Test Mode (%s)", o.UUID) 95 | } else { 96 | o.UUID = guuid.NewString() 97 | logrus.Infof("Starting auditor in Regular Mode (%s)", o.UUID) 98 | } 99 | 100 | logrus.Infof( 101 | //nolint:lll 102 | "Image auditor options: [GCP project: %s, repo URL: %s, repo branch: %s, path: %s, UUID: %s]", 103 | o.ProjectID, 104 | o.RepoURL, 105 | o.RepoBranch, 106 | o.ManifestPath, 107 | o.UUID, 108 | ) 109 | } 110 | 111 | func validateAuditOptions(_ *AuditOptions) error { 112 | // TODO: Validate root options 113 | // TODO: Validate audit options 114 | return nil 115 | } 116 | -------------------------------------------------------------------------------- /internal/legacy/gcloud/token.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 gcloud 18 | 19 | import ( 20 | "encoding/csv" 21 | "fmt" 22 | "io" 23 | "strings" 24 | 25 | "github.com/sirupsen/logrus" 26 | "sigs.k8s.io/release-utils/command" 27 | ) 28 | 29 | // Token is the oauth2 access token used for API calls over HTTP. 30 | type Token string 31 | 32 | // GetServiceAccountToken calls gcloud to get an access token for the specified 33 | // service account. 34 | func GetServiceAccountToken( 35 | serviceAccount string, 36 | useServiceAccount bool, 37 | ) (Token, error) { 38 | logrus.Infof("Obtaining access token for %s", serviceAccount) 39 | args := []string{ 40 | "auth", 41 | "print-access-token", 42 | } 43 | args = MaybeUseServiceAccount(serviceAccount, useServiceAccount, args) 44 | 45 | cmd := command.New("gcloud", args...) 46 | 47 | // We use RunSilentSuccessOutput() to ensure the access token is captured, 48 | // but not displayed in logs. 49 | std, err := cmd.RunSilentSuccessOutput() 50 | // Do not log the token (stdout) on error, because it could 51 | // be that the token was valid, but that Run() failed for 52 | // other reasons. NEVER print the token as part of an error message! 53 | if err != nil { 54 | logrus.Errorf("could not execute cmd %v", cmd) 55 | return "", err 56 | } 57 | 58 | stdout := std.Output() 59 | token := Token(strings.TrimSpace(stdout)) 60 | return token, nil 61 | } 62 | 63 | // MaybeUseServiceAccount injects a '--account=...' argument to the command with 64 | // the given service account. 65 | func MaybeUseServiceAccount( 66 | serviceAccount string, 67 | useServiceAccount bool, 68 | cmd []string, 69 | ) []string { 70 | if useServiceAccount && serviceAccount != "" { 71 | cmd = append(cmd, "") 72 | copy(cmd[2:], cmd[1:]) 73 | cmd[1] = fmt.Sprintf("--account=%v", serviceAccount) 74 | } 75 | return cmd 76 | } 77 | 78 | // ActivateServiceAccounts uses the given CSV of JSON key filepaths to activate 79 | // the associated service accounts. 80 | func ActivateServiceAccounts(keyFilePaths string) error { 81 | r := csv.NewReader(strings.NewReader(keyFilePaths)) 82 | for { 83 | record, err := r.Read() 84 | if err == io.EOF { 85 | break 86 | } 87 | if err != nil { 88 | logrus.Fatal(err) 89 | } 90 | 91 | for _, keyFilePath := range record { 92 | if err := ActivateServiceAccount(keyFilePath); err != nil { 93 | logrus.Fatal(err) 94 | } 95 | } 96 | } 97 | return nil 98 | } 99 | 100 | // ActivateServiceAccount activates the service account with gcloud. 101 | func ActivateServiceAccount(keyFilePath string) error { 102 | cmd := command.New( 103 | "gcloud", 104 | "auth", 105 | "activate-service-account", 106 | "--key-file="+keyFilePath, 107 | ) 108 | 109 | return cmd.RunSuccess() 110 | } 111 | -------------------------------------------------------------------------------- /promoter/file/file.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes 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 file 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "io" 23 | "os" 24 | 25 | "github.com/sirupsen/logrus" 26 | "sigs.k8s.io/release-utils/hash" 27 | 28 | api "sigs.k8s.io/promo-tools/v4/api/files" 29 | ) 30 | 31 | // SyncFileInfo tracks a file during the synchronization operation. 32 | type SyncFileInfo struct { 33 | RelativePath string 34 | AbsolutePath string 35 | 36 | // Some backends (GCS and S3) expose the MD5 of the content in metadata 37 | // This can allow skipping unnecessary copies. 38 | // Note: with multipart uploads or compression, the value is unobvious. 39 | MD5 string 40 | 41 | Size int64 42 | 43 | filestore syncFilestore 44 | } 45 | 46 | // copyFileOp manages copying a single file. 47 | type copyFileOp struct { 48 | Source *SyncFileInfo 49 | Dest *SyncFileInfo 50 | 51 | ManifestFile *api.File 52 | } 53 | 54 | // Run implements SyncFileOp.Run. 55 | func (o *copyFileOp) Run(ctx context.Context) error { 56 | // Download to our temp file 57 | f, err := os.CreateTemp("", "promoter") 58 | if err != nil { 59 | return fmt.Errorf("error creating temp file: %v", err) 60 | } 61 | tempFilename := f.Name() 62 | 63 | defer func() { 64 | if f != nil { 65 | if err := f.Close(); err != nil { 66 | logrus.Warnf( 67 | "error closing temp file %q: %v", 68 | tempFilename, err) 69 | } 70 | } 71 | 72 | if err := os.Remove(tempFilename); err != nil { 73 | logrus.Warnf( 74 | "unable to remove temp file %q: %v", 75 | tempFilename, err) 76 | } 77 | }() 78 | 79 | in, err := o.Source.filestore.OpenReader(ctx, o.Source.RelativePath) 80 | if err != nil { 81 | return fmt.Errorf("error reading %q: %v", o.Source.AbsolutePath, err) 82 | } 83 | defer in.Close() 84 | 85 | if _, err := io.Copy(f, in); err != nil { 86 | return fmt.Errorf( 87 | "error downloading %s: %v", 88 | o.Source.AbsolutePath, err) 89 | } 90 | // We close the file to be sure it is fully written 91 | if err := f.Close(); err != nil { 92 | return fmt.Errorf("error writing temp file %q: %v", tempFilename, err) 93 | } 94 | f = nil 95 | 96 | // Verify the source hash 97 | sha256, err := hash.SHA256ForFile(tempFilename) 98 | if err != nil { 99 | return err 100 | } 101 | if sha256 != o.ManifestFile.SHA256 { 102 | return fmt.Errorf( 103 | "sha256 did not match for file %q: actual=%q expected=%q", 104 | o.Source.AbsolutePath, sha256, o.ManifestFile.SHA256) 105 | } 106 | 107 | // Upload to the destination 108 | return o.Dest.filestore.UploadFile(ctx, o.Dest.RelativePath, tempFilename) 109 | } 110 | 111 | // String is the pretty-printer for an operation, as used by dry-run. 112 | func (o *copyFileOp) String() string { 113 | return fmt.Sprintf( 114 | "COPY %q to %q", 115 | o.Source.AbsolutePath, o.Dest.AbsolutePath) 116 | } 117 | -------------------------------------------------------------------------------- /test-e2e/cip/tests.yaml: -------------------------------------------------------------------------------- 1 | # TODO: Add more nuanced promotion cases, such as: 2 | # 3 | # - rebases 4 | # - tag restoration (move tag back from one Digest to another) 5 | - name: "sanity (basic promotion on single manifest file)" 6 | registries: 7 | - name: gcr.io/k8s-staging-cip-test 8 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 9 | - name: us.gcr.io/k8s-cip-test-prod 10 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 11 | - name: eu.gcr.io/k8s-cip-test-prod 12 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 13 | - name: asia.gcr.io/k8s-cip-test-prod 14 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 15 | invocation: 16 | - "--manifest=$PWD/test-e2e/cip/fixture/sanity/promoter-manifest.yaml" 17 | snapshots: 18 | - name: us.gcr.io/k8s-cip-test-prod/e2e/some/subdir 19 | before: [] 20 | after: &golden-images 21 | - name: foo 22 | dmap: 23 | sha256:b7efc8e6778e8e096d527862365ca8e7f351d1555e11bcf7d230a18b9ba72b34: 24 | - 1.0 25 | sha256:64b870d27522b175b4778d4bf57b5f7f2495db4269bf227aa193f435272644e2: 26 | - 1.0-linux_amd64 27 | sha256:d66f4b0bab4061ef6244f93bea2d414923e7504d92c77a91998c23f909033b02: 28 | - 1.0-linux_s390x 29 | sha256:27ba895d293e5e3192b2bb57f0126f923b48d20221bb017e899ca3f5af74f738: [] 30 | - name: eu.gcr.io/k8s-cip-test-prod/e2e/some/subdir 31 | before: [] 32 | after: *golden-images 33 | - name: asia.gcr.io/k8s-cip-test-prod/e2e/some/subdir 34 | before: [] 35 | after: *golden-images 36 | - name: "recursive-thin" 37 | registries: 38 | - name: gcr.io/k8s-staging-cip-test 39 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 40 | - name: us.gcr.io/k8s-cip-test-prod 41 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 42 | - name: eu.gcr.io/k8s-cip-test-prod 43 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 44 | - name: asia.gcr.io/k8s-cip-test-prod 45 | service-account: k8s-infra-gcr-promoter@k8s-cip-test-prod.iam.gserviceaccount.com 46 | invocation: 47 | - "--thin-manifest-dir=$PWD/test-e2e/cip/fixture/recursive-thin" 48 | snapshots: 49 | - name: us.gcr.io/k8s-cip-test-prod/e2e/golden-bar 50 | before: [] 51 | after: &golden-images-recursive-bar 52 | - name: bar 53 | dmap: 54 | sha256:dd19dc426fa901c12e9a2eeeef8d9ad6c24f50840b8121ccffbba40b5500cb5b: 55 | - 1.0 56 | - name: eu.gcr.io/k8s-cip-test-prod/e2e/golden-bar 57 | before: [] 58 | after: *golden-images-recursive-bar 59 | - name: asia.gcr.io/k8s-cip-test-prod/e2e/golden-bar 60 | before: [] 61 | after: *golden-images-recursive-bar 62 | - name: us.gcr.io/k8s-cip-test-prod/e2e/golden-foo 63 | before: [] 64 | after: &golden-images-recursive-foo 65 | - name: foo 66 | dmap: 67 | sha256:b7efc8e6778e8e096d527862365ca8e7f351d1555e11bcf7d230a18b9ba72b34: 68 | - 1.0 69 | sha256:64b870d27522b175b4778d4bf57b5f7f2495db4269bf227aa193f435272644e2: 70 | - 1.0-linux_amd64 71 | sha256:d66f4b0bab4061ef6244f93bea2d414923e7504d92c77a91998c23f909033b02: 72 | - 1.0-linux_s390x 73 | sha256:27ba895d293e5e3192b2bb57f0126f923b48d20221bb017e899ca3f5af74f738: [] 74 | - name: eu.gcr.io/k8s-cip-test-prod/e2e/golden-foo 75 | before: [] 76 | after: *golden-images-recursive-foo 77 | - name: asia.gcr.io/k8s-cip-test-prod/e2e/golden-foo 78 | before: [] 79 | after: *golden-images-recursive-foo 80 | -------------------------------------------------------------------------------- /cmd/verify-gcr-quota/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes 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 main 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "math/rand" 23 | "net/http" 24 | "runtime" 25 | "time" 26 | ) 27 | 28 | const ( 29 | // targetStatusCode is the GCR status code for too many request. 30 | // Source: https://cloud.google.com/docs/quota#quota_errors 31 | targetStatusCode int = 429 32 | ) 33 | 34 | var queries = []string{ 35 | "https://us.gcr.io/v2/k8s-artifacts-prod/addon-builder/tags/list", 36 | "https://gcr.io/v2/k8s-staging-autoscaling/addon-resizer-amd64/tags/list", 37 | "https://gcr.io/v2/k8s-staging-cloud-provider-gcp/gcp-filestore-csi-driver/tags/list", 38 | } 39 | 40 | // message holds information about the HTTP response. 41 | type message struct { 42 | body string 43 | statusCode int 44 | } 45 | 46 | // main attempts to exceed the quota limits of GCR by continuously 47 | // sending HTTP requests until the quota is reached. 48 | func main() { 49 | // Determine how many workers should be used. 50 | numWorkers := runtime.NumCPU() * 2 51 | c := make(chan message, numWorkers) 52 | 53 | // Create all the workers. 54 | start := time.Now() 55 | spawnWorkers(numWorkers, c) 56 | requests := numWorkers 57 | 58 | // Continuously make HTTP requests. 59 | for { 60 | // Reveal the total number of HTTP requests so far. 61 | fmt.Println("Requests: ", requests) 62 | 63 | // Wait for response. 64 | msg := <-c 65 | if msg.statusCode == targetStatusCode { 66 | t := time.Now() 67 | elapsed := t.Sub(start) 68 | fmt.Println("We were throttled by GCR!") 69 | fmt.Println("Unique Endpoints: ", len(queries)) 70 | fmt.Println("Took: ", elapsed.Minutes(), "minutes") 71 | fmt.Println("Status Code: ", targetStatusCode) 72 | fmt.Println("Body: ", msg.body) 73 | fmt.Println("Time to ") 74 | break 75 | } 76 | 77 | // Spawn a new worker. 78 | go worker(c) 79 | requests++ 80 | } 81 | } 82 | 83 | // getRandQuery randomly selects a query from the list of queries. 84 | func getRandQuery() string { 85 | i := rand.Intn(len(queries)) //nolint: gosec 86 | return queries[i] 87 | } 88 | 89 | // spawnWorkers invokes n concurrent workers. 90 | func spawnWorkers(n int, c chan message) { 91 | for n > 0 { 92 | go worker(c) 93 | n-- 94 | } 95 | } 96 | 97 | // worker sends an HTTP request to GCR and forwards the 98 | // response to the given channel. 99 | func worker(c chan message) { 100 | query := getRandQuery() 101 | resp, err := http.Get(query) //nolint: gosec 102 | if err != nil { 103 | fmt.Println("Encountered an error during HTTP GET request: ", err) 104 | } 105 | 106 | defer resp.Body.Close() 107 | 108 | // Parse the request. 109 | bodyBytes, err := io.ReadAll(resp.Body) 110 | if err != nil { 111 | fmt.Println("Encountered an error when reading response body: ", err) 112 | } 113 | 114 | // Build the response message. 115 | c <- message{ 116 | body: string(bodyBytes), 117 | statusCode: resp.StatusCode, 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /docs/file-promotion.md: -------------------------------------------------------------------------------- 1 | # File promotion 2 | 3 | The file promoter copies files from source GCS buckets to one or more 4 | destination buckets, by reading a `Manifest` file (in YAML). 5 | 6 | The `Manifest` lists the files and their hashes that should be copied from src to 7 | dest. 8 | 9 | Example `Manifest` for files: 10 | 11 | ```yaml 12 | filestores: 13 | - base: gs://staging/ 14 | src: true 15 | - base: gs://prod/subdir 16 | service-account: foo@google-containers.iam.gserviceaccount.com 17 | files: 18 | - name: vegetables/artichoke 19 | sha256: 2d4f26491e0e470236f73a0b8d6828db017eab988cd102fc19afe31f1f56aff7 20 | - name: vegetables/beetroot 21 | sha256: 160b98e27ec99f77efe01e2996fa386f2b2aec552599f8bd861be0a857e7f29f 22 | ``` 23 | 24 | `filestores` is the equivalent of the container manifest `registries`, and lists 25 | the buckets from which the promoter should read or write files. `files` is the 26 | equivalent of `images`, and lists the files that should be promoted. 27 | 28 | `filestores` supports `service-account`, and it also supports relative paths - 29 | note that the source files in the example above are in the root of the bucket, 30 | but they are copied into a subdirectory of the target bucket. 31 | 32 | `files` is a list of files to be copied. The `name` is appended to the base of 33 | the filestore, and then the files are copied. If the source file does not have 34 | the matching sha256, it will not be copied. 35 | 36 | When errors are encountered building the list of files to be copied, no files 37 | will be copied. When errors are encountered while copying files, we will still 38 | attempt to copy remaining files, but the process will report the error. 39 | 40 | Currently only Google Cloud Storage (GCS) buckets supported, with a prefix of 41 | `gs://` 42 | 43 | ## Running the file promoter 44 | 45 | ```console 46 | $ kpromo run files --help 47 | 48 | Promote files from a staging object store to production 49 | 50 | Usage: 51 | kpromo run files [flags] 52 | 53 | Flags: 54 | --confirm initiate a PRODUCTION artifact promotion 55 | --files files path to the files manifest 56 | --filestores filestores path to the filestores promoter manifest 57 | -h, --help help for files 58 | --manifests string path to manifests for multiple projects 59 | --use-service-account allow service account usage with gcloud calls 60 | 61 | Global Flags: 62 | --log-level string the logging verbosity, either 'panic', 'fatal', 'error', 'warning', 'info', 'debug', 'trace' (default "info") 63 | ``` 64 | 65 | ## Generating a file promotion manifest 66 | 67 | This tool will generate a manifest fragment for uploading a set of 68 | files, located in the specified path. 69 | 70 | It takes a single argument `--src`, which is the base of the directory 71 | tree; all files under that directory (recursively) are hashed and 72 | output into the `files` section of a manifest. 73 | 74 | The manifest is written to stdout. 75 | 76 | ```console 77 | $ kpromo manifest files --help 78 | Promote files from a staging object store to production 79 | 80 | Usage: 81 | kpromo manifest files [flags] 82 | 83 | Flags: 84 | -h, --help help for files 85 | --prefix string only export files starting with the provided prefix 86 | --src string the base directory to copy from 87 | 88 | Global Flags: 89 | --log-level string the logging verbosity, either 'panic', 'fatal', 'error', 'warning', 'info', 'debug', 'trace' (default "info") 90 | ``` 91 | 92 | ## Consumers 93 | 94 | - [`kOps`][kops-release-process] 95 | 96 | [kops-release-process]: https://kops.sigs.k8s.io/contributing/release-process/ 97 | -------------------------------------------------------------------------------- /internal/promoter/image/sign_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kubernetes 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 imagepromoter 18 | 19 | import ( 20 | "os" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/require" 24 | "sigs.k8s.io/release-utils/env" 25 | 26 | reg "sigs.k8s.io/promo-tools/v4/internal/legacy/dockerregistry" 27 | "sigs.k8s.io/promo-tools/v4/internal/legacy/dockerregistry/registry" 28 | options "sigs.k8s.io/promo-tools/v4/promoter/image/options" 29 | ) 30 | 31 | // TestGetIdentityToken tests the identity token generation logic. By default 32 | // it will test using the testSigningAccount defined in sign.go. For local testing 33 | // purposes you can override the target account with another one setting 34 | // TEST_SERVICE_ACCOUNT and accessing it with an identity set in a credentials 35 | // file in CIP_E2E_KEY_FILE. 36 | func TestGetIdentityToken(t *testing.T) { 37 | // This unit needs a valid credentials to run 38 | if os.Getenv("CIP_E2E_KEY_FILE") == "" { 39 | return 40 | } 41 | 42 | opts := &options.Options{ 43 | SignerInitCredentials: os.Getenv("CIP_E2E_KEY_FILE"), 44 | } 45 | 46 | di := DefaultPromoterImplementation{} 47 | _, err := di.GetIdentityToken(opts, "fakeAccount@iam.project..") 48 | require.Error(t, err) 49 | 50 | tok, err := di.GetIdentityToken( 51 | opts, env.Default("TEST_SERVICE_ACCOUNT", TestSigningAccount), 52 | ) 53 | 54 | require.NoError(t, err) 55 | require.NotEmpty(t, tok) 56 | } 57 | 58 | func TestTargetIdentity(t *testing.T) { 59 | t.Parallel() 60 | 61 | for _, tc := range []struct { 62 | name string 63 | edge *reg.PromotionEdge 64 | assert func(string) 65 | }{ 66 | { 67 | name: "modified reference with real-world example", 68 | edge: ®.PromotionEdge{ 69 | DstRegistry: registry.Context{Name: "us-west2-docker.pkg.dev/k8s-artifacts-prod/images/kubernetes"}, 70 | DstImageTag: reg.ImageTag{Name: "conformance-arm64"}, 71 | Digest: "sha256:709e17a9c17018997724ed19afc18dbf576e9af10dfe78c13b34175027916d8f", 72 | }, 73 | assert: func(res string) { 74 | require.Equal(t, "registry.k8s.io/kubernetes/conformance-arm64", res) 75 | }, 76 | }, 77 | { 78 | name: "modified reference with simple example", 79 | edge: ®.PromotionEdge{ 80 | DstRegistry: registry.Context{Name: "registry/k8s-artifacts-prod/images"}, 81 | DstImageTag: reg.ImageTag{Name: "image"}, 82 | Digest: "sha256", 83 | }, 84 | assert: func(res string) { 85 | require.Equal(t, "registry.k8s.io/image", res) 86 | }, 87 | }, 88 | { 89 | name: "not modified reference", 90 | edge: ®.PromotionEdge{ 91 | DstRegistry: registry.Context{Name: "foo-bar"}, 92 | DstImageTag: reg.ImageTag{Name: "conformance-arm64"}, 93 | Digest: "sha256:709e17a9c17018997724ed19afc18dbf576e9af10dfe78c13b34175027916d8f", 94 | }, 95 | assert: func(res string) { 96 | require.Equal(t, "foo-bar/conformance-arm64", res) 97 | }, 98 | }, 99 | } { 100 | edge := tc.edge 101 | assert := tc.assert 102 | 103 | t.Run(tc.name, func(t *testing.T) { 104 | t.Parallel() 105 | res := targetIdentity(edge) 106 | assert(res) 107 | }) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /test-e2e/golden-images/push-golden.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2021 The Kubernetes 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 | 17 | # About: This script automatest the process of pushing golden iamges for both e2e tests. 18 | # Images are loaded from local archives and pushed to the designated staging repo. When 19 | # passed the --audit flag, images will be tagged and pushed to 20 | # TEST_AUDIT_STAGING_IMG_REPOSITORY, otherwise defaulting to 21 | # TEST_STAGING_IMG_REPOSITORY (both defined in workspace_status.sh). 22 | # 23 | # Usage: 24 | # ./push-golden.sh [--audit] 25 | 26 | set -o errexit 27 | set -o nounset 28 | set -o pipefail 29 | 30 | printUsage() { 31 | >&2 echo "Usage: $0 [--audit]" 32 | } 33 | 34 | repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P) 35 | archive_path="${repo_root}/test-e2e/golden-images/archives" 36 | # Inject workspace variables 37 | source <(${repo_root}/workspace_status.sh inject) 38 | staging_repo="$TEST_STAGING_IMG_REPOSITORY" 39 | 40 | if [[ $# == 1 ]]; then 41 | if [[ "$1" == --audit ]]; then 42 | staging_repo="$TEST_AUDIT_STAGING_IMG_REPOSITORY" 43 | else 44 | >&2 echo "ERROR: Malformed flag!" 45 | printUsage 46 | exit 1 47 | fi 48 | elif (( $# > 1 )); then 49 | >&2 echo "ERROR: Invalid number of arguments!" 50 | printUsage 51 | exit 1 52 | fi 53 | 54 | # Load archives. 55 | docker load -i "${archive_path}/bar/1.0.tar" 56 | docker load -i "${archive_path}/foo/1.0-linux_amd64.tar" 57 | docker load -i "${archive_path}/foo/1.0-linux_s390x.tar" 58 | docker load -i "${archive_path}/foo/NOTAG-0.tar" 59 | 60 | # Re-tag images (only for auditor) 61 | if [[ "$staging_repo" == "$TEST_AUDIT_STAGING_IMG_REPOSITORY" ]]; then 62 | docker tag "${TEST_STAGING_IMG_REPOSITORY}/golden-bar/bar:1.0" "${staging_repo}/golden-bar/bar:1.0" 63 | docker tag "${TEST_STAGING_IMG_REPOSITORY}/golden-foo/foo:1.0-linux_amd64" "${staging_repo}/golden-foo/foo:1.0-linux_amd64" 64 | docker tag "${TEST_STAGING_IMG_REPOSITORY}/golden-foo/foo:1.0-linux_s390x" "${staging_repo}/golden-foo/foo:1.0-linux_s390x" 65 | docker tag "${TEST_STAGING_IMG_REPOSITORY}/golden-foo/foo:NOTAG-0" "${staging_repo}/golden-foo/foo:NOTAG-0" 66 | fi 67 | 68 | # Push to k8s-staging-cip-test. 69 | docker push "${staging_repo}/golden-bar/bar:1.0" 70 | docker push "${staging_repo}/golden-foo/foo:1.0-linux_amd64" 71 | docker push "${staging_repo}/golden-foo/foo:1.0-linux_s390x" 72 | docker push "${staging_repo}/golden-foo/foo:NOTAG-0" 73 | 74 | # Create a manifest. 75 | docker manifest create \ 76 | "${staging_repo}/golden-foo/foo:1.0" \ 77 | "${staging_repo}/golden-foo/foo:1.0-linux_amd64" \ 78 | "${staging_repo}/golden-foo/foo:1.0-linux_s390x" 79 | 80 | # Fixup the s390x image because it's set to amd64 by default. 81 | docker manifest annotate --arch=s390x \ 82 | "${staging_repo}/golden-foo/foo:1.0" \ 83 | "${staging_repo}/golden-foo/foo:1.0-linux_s390x" 84 | 85 | # Show manifest for debugging. 86 | docker manifest inspect "${staging_repo}/golden-foo/foo:1.0" 87 | 88 | # Push the manifest list. 89 | docker manifest push --purge "${staging_repo}/golden-foo/foo:1.0" 90 | 91 | # Remove tag for tagless image. 92 | gcloud container images untag --quiet "${staging_repo}/golden-foo/foo:NOTAG-0" 93 | -------------------------------------------------------------------------------- /promoter/file/filefakes/fake_sync_file_op.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes 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 counterfeiter. DO NOT EDIT. 18 | package filefakes 19 | 20 | import ( 21 | "context" 22 | "sync" 23 | 24 | "sigs.k8s.io/promo-tools/v4/promoter/file" 25 | ) 26 | 27 | type FakeSyncFileOp struct { 28 | RunStub func(context.Context) error 29 | runMutex sync.RWMutex 30 | runArgsForCall []struct { 31 | arg1 context.Context 32 | } 33 | runReturns struct { 34 | result1 error 35 | } 36 | runReturnsOnCall map[int]struct { 37 | result1 error 38 | } 39 | invocations map[string][][]interface{} 40 | invocationsMutex sync.RWMutex 41 | } 42 | 43 | func (fake *FakeSyncFileOp) Run(arg1 context.Context) error { 44 | fake.runMutex.Lock() 45 | ret, specificReturn := fake.runReturnsOnCall[len(fake.runArgsForCall)] 46 | fake.runArgsForCall = append(fake.runArgsForCall, struct { 47 | arg1 context.Context 48 | }{arg1}) 49 | stub := fake.RunStub 50 | fakeReturns := fake.runReturns 51 | fake.recordInvocation("Run", []interface{}{arg1}) 52 | fake.runMutex.Unlock() 53 | if stub != nil { 54 | return stub(arg1) 55 | } 56 | if specificReturn { 57 | return ret.result1 58 | } 59 | return fakeReturns.result1 60 | } 61 | 62 | func (fake *FakeSyncFileOp) RunCallCount() int { 63 | fake.runMutex.RLock() 64 | defer fake.runMutex.RUnlock() 65 | return len(fake.runArgsForCall) 66 | } 67 | 68 | func (fake *FakeSyncFileOp) RunCalls(stub func(context.Context) error) { 69 | fake.runMutex.Lock() 70 | defer fake.runMutex.Unlock() 71 | fake.RunStub = stub 72 | } 73 | 74 | func (fake *FakeSyncFileOp) RunArgsForCall(i int) context.Context { 75 | fake.runMutex.RLock() 76 | defer fake.runMutex.RUnlock() 77 | argsForCall := fake.runArgsForCall[i] 78 | return argsForCall.arg1 79 | } 80 | 81 | func (fake *FakeSyncFileOp) RunReturns(result1 error) { 82 | fake.runMutex.Lock() 83 | defer fake.runMutex.Unlock() 84 | fake.RunStub = nil 85 | fake.runReturns = struct { 86 | result1 error 87 | }{result1} 88 | } 89 | 90 | func (fake *FakeSyncFileOp) RunReturnsOnCall(i int, result1 error) { 91 | fake.runMutex.Lock() 92 | defer fake.runMutex.Unlock() 93 | fake.RunStub = nil 94 | if fake.runReturnsOnCall == nil { 95 | fake.runReturnsOnCall = make(map[int]struct { 96 | result1 error 97 | }) 98 | } 99 | fake.runReturnsOnCall[i] = struct { 100 | result1 error 101 | }{result1} 102 | } 103 | 104 | func (fake *FakeSyncFileOp) Invocations() map[string][][]interface{} { 105 | fake.invocationsMutex.RLock() 106 | defer fake.invocationsMutex.RUnlock() 107 | copiedInvocations := map[string][][]interface{}{} 108 | for key, value := range fake.invocations { 109 | copiedInvocations[key] = value 110 | } 111 | return copiedInvocations 112 | } 113 | 114 | func (fake *FakeSyncFileOp) recordInvocation(key string, args []interface{}) { 115 | fake.invocationsMutex.Lock() 116 | defer fake.invocationsMutex.Unlock() 117 | if fake.invocations == nil { 118 | fake.invocations = map[string][][]interface{}{} 119 | } 120 | if fake.invocations[key] == nil { 121 | fake.invocations[key] = [][]interface{}{} 122 | } 123 | fake.invocations[key] = append(fake.invocations[key], args) 124 | } 125 | 126 | var _ file.SyncFileOp = new(FakeSyncFileOp) 127 | --------------------------------------------------------------------------------