diff --git a/.github/workflows/checkton.yaml b/.github/workflows/checkton.yaml index d1d30ea46a..3a1ec1421c 100644 --- a/.github/workflows/checkton.yaml +++ b/.github/workflows/checkton.yaml @@ -21,6 +21,7 @@ jobs: # Set to false when re-enabling SARIF uploads fail-on-findings: true find-copies-harder: true + exclude-regex: ^ta-generator # Currently, code scanning alerts annoyingly stay open even if you fix them. # Don't upload SARIF until https://github.com/orgs/community/discussions/132787 is resolved. diff --git a/.github/workflows/go-ci.yaml b/.github/workflows/go-ci.yaml index 8dfbd553aa..5e3b55eebb 100644 --- a/.github/workflows/go-ci.yaml +++ b/.github/workflows/go-ci.yaml @@ -6,27 +6,39 @@ jobs: lint: name: Lint runs-on: ubuntu-latest + strategy: + matrix: + path: + - task-generator + - ta-generator steps: - uses: actions/checkout@9a9194f87191a7e9055e3e9b95b8cfb13023bb08 - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 with: - go-version-file: './task-generator/go.mod' + go-version-file: './${{matrix.path}}/go.mod' + cache-dependency-path: ./${{matrix.path}}/go.sum - name: golangci-lint uses: golangci/golangci-lint-action@e13590484647602042886c51e2a8e43a670a22b7 with: - working-directory: task-generator + working-directory: ${{matrix.path}} args: "--timeout=10m --build-tags='normal periodic'" go: name: Check sources runs-on: ubuntu-latest + strategy: + matrix: + path: + - task-generator + - ta-generator steps: - uses: actions/checkout@9a9194f87191a7e9055e3e9b95b8cfb13023bb08 - name: Install Go uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 with: - go-version-file: './task-generator/go.mod' + go-version-file: './${{matrix.path}}/go.mod' + cache-dependency-path: ./${{matrix.path}}/go.sum - name: Check go mod status - working-directory: task-generator + working-directory: ${{matrix.path}} run: | go mod tidy if [[ ! -z $(git status -s) ]] @@ -36,7 +48,7 @@ jobs: exit 1 fi - name: Check format - working-directory: task-generator + working-directory: ${{matrix.path}} run: | go fmt ./... @@ -46,21 +58,31 @@ jobs: git --no-pager diff exit 1 fi + - name: Tests + working-directory: ${{matrix.path}} + run: | + go test ./... security_scan: name: Security scan runs-on: ubuntu-latest + strategy: + matrix: + path: + - task-generator + - ta-generator steps: - uses: actions/checkout@9a9194f87191a7e9055e3e9b95b8cfb13023bb08 - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 with: - go-version-file: './task-generator/go.mod' + go-version-file: './${{matrix.path}}/go.mod' + cache-dependency-path: ./${{matrix.path}}/go.sum # https://github.com/securego/gosec/blob/12be14859bc7d4b956b71bef0b443694aa519d8a/README.md#integrating-with-code-scanning - name: Run Gosec Security Scanner uses: securego/gosec@master with: # we let the report trigger content trigger a failure using the GitHub Security features. - args: '-tags normal,periodic -no-fail -fmt sarif -out results.sarif ./...' + args: '-tags normal,periodic -no-fail -fmt sarif -out results.sarif ${{matrix.path}}/...' - name: Upload SARIF file uses: github/codeql-action/upload-sarif@9c646c24a4c8410122b0d6a1311088e9377eea95 with: diff --git a/hack/generate-ta-tasks.sh b/hack/generate-ta-tasks.sh index 40fc6d2ecf..0f001a5237 100755 --- a/hack/generate-ta-tasks.sh +++ b/hack/generate-ta-tasks.sh @@ -1,29 +1,26 @@ #!/usr/bin/env bash set -o errexit +set -o errtrace set -o nounset set -o pipefail set -o posix shopt -s globstar nullglob +command -v go &> /dev/null || { echo Please install golang to run this tool; exit 1; } +[[ "$(go env GOVERSION)" == @(go1|go1.[1-9]+(|.*|rc*|beta*)|go1.1[0-9]+(|.*|rc*|beta*)|go1.20*) ]] && { echo Please install golang 1.21.0 or newer; exit 1; } + HACK_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" ROOT_DIR="$(git rev-parse --show-toplevel)" TASK_DIR="$(realpath "${ROOT_DIR}/task")" -if ! command -v tash &> /dev/null; then - echo INFO: tash command is not available will download and use the latest version - tash_dir="$(mktemp -d)" - trap 'rm -rf ${tash_dir}' EXIT - tash_url=https://github.com/enterprise-contract/hacks/releases/download/latest/tash - echo INFO: downloading from ${tash_url} to "${tash_dir}" - curl --no-progress-meter --location --output "${tash_dir}/tash" "${tash_url}" - echo INFO: SHA256: "$(sha256sum "${tash_dir}/tash")" - chmod +x "${tash_dir}/tash" - tash() { - "${tash_dir}/tash" "$@" - } -fi +tashbin="$(mktemp --dry-run)" +GOTOOLCHAIN=auto go build -C "${ROOT_DIR}/ta-generator/" -o "${tashbin}" +trap 'rm "${tashbin}"' EXIT +tash() { + "${tashbin}" "$@" +} declare -i changes=0 emit() { @@ -44,7 +41,8 @@ fi cd "${TASK_DIR}" for recipe_path in **/recipe.yaml; do task_path="${recipe_path%/recipe.yaml}/$(basename "${recipe_path%/*/*}").yaml" - cat <<< "$(tash "${recipe_path}")" > "${task_path}" + sponge=$(tash "${TASK_DIR}/${recipe_path}") + echo "${sponge}" > "${task_path}" readme_path="${recipe_path%/recipe.yaml}/README.md" "${HACK_DIR}/generate-readme.sh" "${task_path}" > "${readme_path}" if ! git diff --quiet HEAD "${task_path}"; then diff --git a/ta-generator/README.md b/ta-generator/README.md new file mode 100644 index 0000000000..bd39ab7c22 --- /dev/null +++ b/ta-generator/README.md @@ -0,0 +1,17 @@ +# Trusted Artifacts variants generator + +## Description and usage + +The code in here will process a set of directions in `recipe.yaml` file and +based on that and a set of builtin conventions generate the Tekton Task +definition in YAML format. + +Usage (from this directory): + + go run . path/to/recipe.yaml + +The generated Trusted Artifacts Task is provided on the standard output. + +## Development + +To build the tool executable run `go build`, to run the tests run `go test`. diff --git a/ta-generator/expect.go b/ta-generator/expect.go new file mode 100644 index 0000000000..114caaacd5 --- /dev/null +++ b/ta-generator/expect.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "os" +) + +func expect(err error) { + if err == nil { + return + } + fmt.Fprint(os.Stderr, err) + os.Exit(1) +} + +func expectValue[T any](val T, err error) T { + if err != nil { + expect(err) + } + + return val +} diff --git a/ta-generator/go.mod b/ta-generator/go.mod new file mode 100644 index 0000000000..8b70d3967c --- /dev/null +++ b/ta-generator/go.mod @@ -0,0 +1,91 @@ +module github.com/konflux-ci/build-definitions/ta-generator + +go 1.22.3 + +require ( + github.com/google/go-cmp v0.6.0 + github.com/google/go-containerregistry v0.19.1 + github.com/tektoncd/pipeline v0.60.1 + github.com/zregvart/tkn-fmt v0.0.0-20240613172239-ba04662da674 + k8s.io/api v0.30.1 + mvdan.cc/sh/v3 v3.8.0 + sigs.k8s.io/yaml v1.4.0 +) + +require ( + contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d // indirect + contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blendle/zapdriver v1.3.1 // indirect + github.com/braydonk/yaml v0.7.0 // indirect + github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/docker/cli v24.0.7+incompatible // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/docker v26.0.0+incompatible // indirect + github.com/docker/docker-credential-helpers v0.7.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/cel-go v0.20.1 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.16.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.54.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/statsd_exporter v0.26.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/vbatts/tar-split v0.11.3 // indirect + go.opencensus.io v0.24.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/term v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/api v0.183.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/grpc v1.64.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apimachinery v0.30.1 // indirect + k8s.io/client-go v0.30.1 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a // indirect + k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect + knative.dev/pkg v0.0.0-20240604134003-58135c2c0694 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect +) diff --git a/ta-generator/go.sum b/ta-generator/go.sum new file mode 100644 index 0000000000..095fac6cab --- /dev/null +++ b/ta-generator/go.sum @@ -0,0 +1,722 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d h1:LblfooH1lKOpp1hIhukktmSAxFkqMPFk9KR6iZ0MJNI= +contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= +contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= +contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= +github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= +github.com/braydonk/yaml v0.7.0 h1:ySkqO7r0MGoCNhiRJqE0Xe9yhINMyvOAB3nFjgyJn2k= +github.com/braydonk/yaml v0.7.0/go.mod h1:hcm3h581tudlirk8XEUPDBAimBPbmnL0Y45hCRl47N4= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudevents/sdk-go/v2 v2.15.2 h1:54+I5xQEnI73RBhWHxbI1XJcqOFOVJN85vb41+8mHUc= +github.com/cloudevents/sdk-go/v2 v2.15.2/go.mod h1:lL7kSWAE/V8VI4Wh0jbL2v/jvqsm6tjmaQBSvxcv4uE= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= +github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg= +github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v26.0.0+incompatible h1:Ng2qi+gdKADUa/VM+6b6YaY2nlZhk/lVJiKR/2bMudU= +github.com/docker/docker v26.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= +github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY= +github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk= +github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= +github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8= +github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= +github.com/prometheus/statsd_exporter v0.26.1 h1:ucbIAdPmwAUcA+dU+Opok8Qt81Aw8HanlO+2N/Wjv7w= +github.com/prometheus/statsd_exporter v0.26.1/go.mod h1:XlDdjAmRmx3JVvPPYuFNUg+Ynyb5kR69iPPkQjxXFMk= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= +github.com/tektoncd/pipeline v0.60.1 h1:SRQ0p3LXBMtG4jcLBVmhENjrQIevah9CSo3jEvKpZDk= +github.com/tektoncd/pipeline v0.60.1/go.mod h1:7bCvVXxBB06Ee7GVqYp+UKgUN5IadFvvH9wdLds5jZE= +github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= +github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= +github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zregvart/tkn-fmt v0.0.0-20240613172239-ba04662da674 h1:yspt7cxlsDwWUWUjqbA75dyloOGacbnzHWO55CMsq9k= +github.com/zregvart/tkn-fmt v0.0.0-20240613172239-ba04662da674/go.mod h1:t7139snuK35qgYLly1CaLLVZy6UCkz/KmN/AevOJi+k= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.183.0 h1:PNMeRDwo1pJdgNcFQ9GstuLe/noWKIc89pRWRLMvLwE= +google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= +k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= +k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= +k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= +k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a h1:zD1uj3Jf+mD4zmA7W+goE5TxDkI7OGJjBNBzq5fJtLA= +k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a/go.mod h1:UxDHUPsUwTOOxSU+oXURfFBcAS6JwiRXTYqYwfuGowc= +k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= +k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +knative.dev/pkg v0.0.0-20240604134003-58135c2c0694 h1:trbVXm0WFXxxjcuSCKrYHKQwRQCuDrE8T5+yqXAI1NA= +knative.dev/pkg v0.0.0-20240604134003-58135c2c0694/go.mod h1:E5OxJHFsQiW7pL0lVJ02XEVm2QUc0ZVdSUHiarUJHrU= +mvdan.cc/sh/v3 v3.8.0 h1:ZxuJipLZwr/HLbASonmXtcvvC9HXY9d2lXZHnKGjFc8= +mvdan.cc/sh/v3 v3.8.0/go.mod h1:w04623xkgBVo7/IUK89E0g8hBykgEpN0vgOj3RJr6MY= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/ta-generator/golden/buildah/base.yaml b/ta-generator/golden/buildah/base.yaml new file mode 100644 index 0000000000..5b6d4d19af --- /dev/null +++ b/ta-generator/golden/buildah/base.yaml @@ -0,0 +1,462 @@ +apiVersion: tekton.dev/v1 +kind: Task +metadata: + labels: + app.kubernetes.io/version: "0.1" + build.appstudio.redhat.com/build_type: "docker" + annotations: + tekton.dev/pipelines.minVersion: "0.12.1" + tekton.dev/tags: "image-build, appstudio, hacbs" + name: buildah +spec: + description: |- + Buildah task builds source code into a container image and pushes the image into container registry using buildah tool. + In addition it generates a SBOM file, injects the SBOM file into final container image and pushes the SBOM file as separate image using cosign tool. + When [Java dependency rebuild](https://redhat-appstudio.github.io/docs.stonesoup.io/Documentation/main/cli/proc_enabled_java_dependencies.html) is enabled it triggers rebuilds of Java artifacts. + When prefetch-dependencies task was activated it is using its artifacts to run build in hermetic environment. + params: + - description: Reference of the image buildah will produce. + name: IMAGE + type: string + - default: "" + description: Deprecated. Has no effect. Will be removed in the future. + name: BUILDER_IMAGE + type: string + - default: ./Dockerfile + description: Path to the Dockerfile to build. + name: DOCKERFILE + type: string + - default: . + description: Path to the directory to use as context. + name: CONTEXT + type: string + - default: "true" + description: Verify the TLS on the registry endpoint (for push/pull to a non-TLS registry) + name: TLSVERIFY + type: string + - description: unused, should be removed in next task version + name: DOCKER_AUTH + type: string + default: "" + - default: "false" + description: Determines if build will be executed without network access. + name: HERMETIC + type: string + - default: "" + description: In case it is not empty, the prefetched content should be made available to the build. + name: PREFETCH_INPUT + type: string + - default: "" + description: Delete image tag after specified time. Empty means to keep the image tag. Time values could be something like 1h, 2d, 3w for hours, days, and weeks, respectively. + name: IMAGE_EXPIRES_AFTER + type: string + - name: COMMIT_SHA + description: The image is built from this commit. + type: string + default: "" + - name: YUM_REPOS_D_SRC + description: Path in the git repository in which yum repository files are stored + default: repos.d + - name: YUM_REPOS_D_FETCHED + description: Path in source workspace where dynamically-fetched repos are present + default: fetched.repos.d + - name: YUM_REPOS_D_TARGET + description: Target path on the container in which yum repository files should be made available + default: /etc/yum.repos.d + - name: TARGET_STAGE + description: Target stage in Dockerfile to build. If not specified, the Dockerfile is processed entirely to (and including) its last stage. + type: string + default: "" + - name: ENTITLEMENT_SECRET + description: Name of secret which contains the entitlement certificates + type: string + default: "etc-pki-entitlement" + - name: BUILD_ARGS + description: Array of --build-arg values ("arg=value" strings) + type: array + default: [] + - name: BUILD_ARGS_FILE + description: Path to a file with build arguments, see https://www.mankier.com/1/buildah-build#--build-arg-file + type: string + default: "" + + results: + - description: Digest of the image just built + name: IMAGE_DIGEST + - description: Image repository where the built image was pushed + name: IMAGE_URL + - description: Digests of the base images used for build + name: BASE_IMAGES_DIGESTS + - name: SBOM_JAVA_COMPONENTS_COUNT + description: The counting of Java components by publisher in JSON format + type: string + - name: JAVA_COMMUNITY_DEPENDENCIES + description: The Java dependencies that came from community sources such as Maven central. + stepTemplate: + env: + - name: BUILDAH_FORMAT + value: oci + - name: STORAGE_DRIVER + value: vfs + - name: HERMETIC + value: $(params.HERMETIC) + - name: CONTEXT + value: $(params.CONTEXT) + - name: DOCKERFILE + value: $(params.DOCKERFILE) + - name: IMAGE + value: $(params.IMAGE) + - name: TLSVERIFY + value: $(params.TLSVERIFY) + - name: IMAGE_EXPIRES_AFTER + value: $(params.IMAGE_EXPIRES_AFTER) + - name: YUM_REPOS_D_SRC + value: $(params.YUM_REPOS_D_SRC) + - name: YUM_REPOS_D_FETCHED + value: $(params.YUM_REPOS_D_FETCHED) + - name: YUM_REPOS_D_TARGET + value: $(params.YUM_REPOS_D_TARGET) + - name: TARGET_STAGE + value: $(params.TARGET_STAGE) + - name: PARAM_BUILDER_IMAGE + value: $(params.BUILDER_IMAGE) + - name: ENTITLEMENT_SECRET + value: $(params.ENTITLEMENT_SECRET) + - name: BUILD_ARGS_FILE + value: $(params.BUILD_ARGS_FILE) + + steps: + - image: quay.io/redhat-appstudio/buildah:v1.31.0@sha256:34f12c7b72ec2c28f1ded0c494b428df4791c909f1f174dd21b8ed6a57cf5ddb + name: build + computeResources: + limits: + memory: 4Gi + requests: + memory: 512Mi + cpu: 250m + env: + - name: COMMIT_SHA + value: $(params.COMMIT_SHA) + args: + - $(params.BUILD_ARGS[*]) + script: | + if [ -n "${PARAM_BUILDER_IMAGE}" ]; then + echo "WARNING: provided deprecated BUILDER_IMAGE parameter has no effect." + fi + + SOURCE_CODE_DIR=source + if [ -e "$SOURCE_CODE_DIR/$CONTEXT/$DOCKERFILE" ]; then + dockerfile_path="$(pwd)/$SOURCE_CODE_DIR/$CONTEXT/$DOCKERFILE" + elif [ -e "$SOURCE_CODE_DIR/$DOCKERFILE" ]; then + dockerfile_path="$(pwd)/$SOURCE_CODE_DIR/$DOCKERFILE" + elif echo "$DOCKERFILE" | grep -q "^https\?://"; then + echo "Fetch Dockerfile from $DOCKERFILE" + dockerfile_path=$(mktemp --suffix=-Dockerfile) + http_code=$(curl -s -L -w "%{http_code}" --output "$dockerfile_path" "$DOCKERFILE") + if [ $http_code != 200 ]; then + echo "No Dockerfile is fetched. Server responds $http_code" + exit 1 + fi + http_code=$(curl -s -L -w "%{http_code}" --output "$dockerfile_path.dockerignore.tmp" "$DOCKERFILE.dockerignore") + if [ $http_code = 200 ]; then + echo "Fetched .dockerignore from $DOCKERFILE.dockerignore" + mv "$dockerfile_path.dockerignore.tmp" $SOURCE_CODE_DIR/$CONTEXT/.dockerignore + fi + else + echo "Cannot find Dockerfile $DOCKERFILE" + exit 1 + fi + if [ -n "$JVM_BUILD_WORKSPACE_ARTIFACT_CACHE_PORT_80_TCP_ADDR" ] && grep -q '^\s*RUN \(./\)\?mvn' "$dockerfile_path"; then + sed -i -e "s|^\s*RUN \(\(./\)\?mvn\)\(.*\)|RUN echo \"mirror.defaulthttp://$JVM_BUILD_WORKSPACE_ARTIFACT_CACHE_PORT_80_TCP_ADDR/v1/cache/default/0/*\" > /tmp/settings.yaml; \1 -s /tmp/settings.yaml \3|g" "$dockerfile_path" + touch /var/lib/containers/java + fi + + # Fixing group permission on /var/lib/containers + chown root:root /var/lib/containers + + sed -i 's/^\s*short-name-mode\s*=\s*.*/short-name-mode = "disabled"/' /etc/containers/registries.conf + + # Setting new namespace to run buildah - 2^32-2 + echo 'root:1:4294967294' | tee -a /etc/subuid >> /etc/subgid + + BUILDAH_ARGS=() + + BASE_IMAGES=$(grep -i '^\s*FROM' "$dockerfile_path" | sed 's/--platform=\S*//' | awk '{print $2}') + if [ "${HERMETIC}" == "true" ]; then + BUILDAH_ARGS+=("--pull=never") + UNSHARE_ARGS="--net" + for image in $BASE_IMAGES; do + if [ "${image}" != "scratch" ]; then + unshare -Ufp --keep-caps -r --map-users 1,1,65536 --map-groups 1,1,65536 -- buildah pull $image + fi + done + echo "Build will be executed with network isolation" + fi + + if [ -n "${TARGET_STAGE}" ]; then + BUILDAH_ARGS+=("--target=${TARGET_STAGE}") + fi + + if [ -n "${BUILD_ARGS_FILE}" ]; then + BUILDAH_ARGS+=("--build-arg-file=$(pwd)/$SOURCE_CODE_DIR/${BUILD_ARGS_FILE}") + fi + + for build_arg in "$@"; do + BUILDAH_ARGS+=("--build-arg=$build_arg") + done + + if [ -d "$(workspaces.source.path)/cachi2" ]; then + cp -r "$(workspaces.source.path)/cachi2" /tmp/ + chmod -R go+rwX /tmp/cachi2 + VOLUME_MOUNTS="--volume /tmp/cachi2:/cachi2" + sed -i 's|^\s*run |RUN . /cachi2/cachi2.env \&\& \\\n |i' "$dockerfile_path" + echo "Prefetched content will be made available" + + prefetched_repo_for_my_arch="/tmp/cachi2/output/deps/rpm/$(uname -m)/repos.d/cachi2.repo" + if [ -f "$prefetched_repo_for_my_arch" ]; then + echo "Adding $prefetched_repo_for_my_arch to $YUM_REPOS_D_FETCHED" + mkdir -p "$YUM_REPOS_D_FETCHED" + cp --no-clobber "$prefetched_repo_for_my_arch" "$YUM_REPOS_D_FETCHED" + fi + fi + + # if yum repofiles stored in git, copy them to mount point outside the source dir + if [ -d "${SOURCE_CODE_DIR}/${YUM_REPOS_D_SRC}" ]; then + mkdir -p ${YUM_REPOS_D_FETCHED} + cp -r ${SOURCE_CODE_DIR}/${YUM_REPOS_D_SRC}/* ${YUM_REPOS_D_FETCHED} + fi + + # if anything in the repofiles mount point (either fetched or from git), mount it + if [ -d "${YUM_REPOS_D_FETCHED}" ]; then + chmod -R go+rwX ${YUM_REPOS_D_FETCHED} + mount_point=$(realpath ${YUM_REPOS_D_FETCHED}) + VOLUME_MOUNTS="${VOLUME_MOUNTS} --volume ${mount_point}:${YUM_REPOS_D_TARGET}" + fi + + LABELS=( + "--label" "build-date=$(date -u +'%Y-%m-%dT%H:%M:%S')" + "--label" "architecture=$(uname -m)" + "--label" "vcs-type=git" + ) + [ -n "$COMMIT_SHA" ] && LABELS+=("--label" "vcs-ref=$COMMIT_SHA") + [ -n "$IMAGE_EXPIRES_AFTER" ] && LABELS+=("--label" "quay.expires-after=$IMAGE_EXPIRES_AFTER") + + ENTITLEMENT_PATH="/entitlement" + if [ -d "$ENTITLEMENT_PATH" ]; then + cp -r --preserve=mode "$ENTITLEMENT_PATH" /tmp/entitlement + VOLUME_MOUNTS="${VOLUME_MOUNTS} --volume /tmp/entitlement:/etc/pki/entitlement" + echo "Adding the entitlement to the build" + fi + + unshare -Uf $UNSHARE_ARGS --keep-caps -r --map-users 1,1,65536 --map-groups 1,1,65536 -w ${SOURCE_CODE_DIR}/$CONTEXT -- buildah build \ + $VOLUME_MOUNTS \ + "${BUILDAH_ARGS[@]}" \ + "${LABELS[@]}" \ + --tls-verify=$TLSVERIFY --no-cache \ + --ulimit nofile=4096:4096 \ + -f "$dockerfile_path" -t $IMAGE . + + container=$(buildah from --pull-never $IMAGE) + buildah mount $container | tee /workspace/container_path + echo $container > /workspace/container_name + + # Save the SBOM produced by Cachi2 so it can be merged into the final SBOM later + if [ -d "$(workspaces.source.path)/cachi2" ]; then + cp /tmp/cachi2/output/bom.json ./sbom-cachi2.json + fi + + # Expose base image digests + for image in $BASE_IMAGES; do + if [ "${image}" != "scratch" ]; then + buildah images --format '{{ .Name }}:{{ .Tag }}@{{ .Digest }}' --filter reference="$image" >> $(results.BASE_IMAGES_DIGESTS.path) + fi + done + + # Needed to generate base images SBOM + echo "$BASE_IMAGES" > $(workspaces.source.path)/base_images_from_dockerfile + + securityContext: + capabilities: + add: + - SETFCAP + volumeMounts: + - mountPath: /var/lib/containers + name: varlibcontainers + - mountPath: "/entitlement" + name: etc-pki-entitlement + workingDir: $(workspaces.source.path) + + - name: sbom-syft-generate + image: quay.io/redhat-appstudio/syft:v0.105.1@sha256:1910b829997650c696881e5fc2fc654ddf3184c27edb1b2024e9cb2ba51ac431 + # Respect Syft configuration if the user has it in the root of their repository + # (need to set the workdir, see https://github.com/anchore/syft/issues/2465) + workingDir: $(workspaces.source.path)/source + script: | + echo "Running syft on the source directory" + syft dir:$(workspaces.source.path)/source --output cyclonedx-json=$(workspaces.source.path)/sbom-source.json + find $(cat /workspace/container_path) -xtype l -delete + echo "Running syft on the image filesystem" + syft dir:$(cat /workspace/container_path) --output cyclonedx-json=$(workspaces.source.path)/sbom-image.json + volumeMounts: + - mountPath: /var/lib/containers + name: varlibcontainers + - name: analyse-dependencies-java-sbom + image: quay.io/redhat-appstudio/hacbs-jvm-build-request-processor:127ee0c223a2b56a9bd20a6f2eaeed3bd6015f77 + script: | + if [ -f /var/lib/containers/java ]; then + /opt/jboss/container/java/run/run-java.sh analyse-dependencies path $(cat /workspace/container_path) -s $(workspaces.source.path)/sbom-image.json --task-run-name $(context.taskRun.name) --publishers $(results.SBOM_JAVA_COMPONENTS_COUNT.path) + sed -i 's/^/ /' $(results.SBOM_JAVA_COMPONENTS_COUNT.path) # Workaround for SRVKP-2875 + else + touch $(results.JAVA_COMMUNITY_DEPENDENCIES.path) + fi + volumeMounts: + - mountPath: /var/lib/containers + name: varlibcontainers + securityContext: + runAsUser: 0 + + - name: merge-syft-sboms + image: registry.access.redhat.com/ubi9/python-39:1-172.1712567222@sha256:c96f839e927c52990143df4efb2872946fcd5de9e1ed2014947bb2cf3084c27a + script: | + #!/bin/python3 + import json + + # load SBOMs + with open("./sbom-image.json") as f: + image_sbom = json.load(f) + + with open("./sbom-source.json") as f: + source_sbom = json.load(f) + + # fetch unique components from available SBOMs + def get_identifier(component): + return component["name"] + '@' + component.get("version", "") + + image_sbom_components = image_sbom.get("components", []) + existing_components = [get_identifier(component) for component in image_sbom_components] + + source_sbom_components = source_sbom.get("components", []) + for component in source_sbom_components: + if get_identifier(component) not in existing_components: + image_sbom_components.append(component) + existing_components.append(get_identifier(component)) + + image_sbom_components.sort(key=lambda c: get_identifier(c)) + + # write the CycloneDX unified SBOM + with open("./sbom-cyclonedx.json", "w") as f: + json.dump(image_sbom, f, indent=4) + workingDir: $(workspaces.source.path) + securityContext: + runAsUser: 0 + + - name: merge-cachi2-sbom + image: quay.io/redhat-appstudio/cachi2:0.7.0@sha256:1fc772aa3636fd0b43d62120d832e5913843e028e8cac42814b487c3a0a32bd8 + script: | + if [ -d "$(workspaces.source.path)/cachi2" ]; then + echo "Merging contents of sbom-cachi2.json into sbom-cyclonedx.json" + /src/utils/merge_syft_sbom.py sbom-cachi2.json sbom-cyclonedx.json > sbom-temp.json + mv sbom-temp.json sbom-cyclonedx.json + else + echo "Skipping step since no Cachi2 SBOM was produced" + fi + workingDir: $(workspaces.source.path) + securityContext: + runAsUser: 0 + + - name: create-purl-sbom + image: registry.access.redhat.com/ubi9/python-39:1-172.1712567222@sha256:c96f839e927c52990143df4efb2872946fcd5de9e1ed2014947bb2cf3084c27a + script: | + #!/bin/python3 + import json + + with open("./sbom-cyclonedx.json") as f: + cyclonedx_sbom = json.load(f) + + purls = [{"purl": component["purl"]} for component in cyclonedx_sbom.get("components", []) if "purl" in component] + purl_content = {"image_contents": {"dependencies": purls}} + + with open("sbom-purl.json", "w") as output_file: + json.dump(purl_content, output_file, indent=4) + workingDir: $(workspaces.source.path) + securityContext: + runAsUser: 0 + + - name: create-base-images-sbom + image: quay.io/redhat-appstudio/base-images-sbom-script@sha256:667669e3def018f9dbb8eaf8868887a40bc07842221e9a98f6787edcff021840 + env: + - name: BASE_IMAGES_DIGESTS_PATH + value: $(results.BASE_IMAGES_DIGESTS.path) + script: | + python3 /app/base_images_sbom_script.py --sbom=sbom-cyclonedx.json --base-images-from-dockerfile=base_images_from_dockerfile --base-images-digests=$BASE_IMAGES_DIGESTS_PATH + workingDir: $(workspaces.source.path) + securityContext: + runAsUser: 0 + + - name: inject-sbom-and-push + image: quay.io/redhat-appstudio/buildah:v1.31.0@sha256:34f12c7b72ec2c28f1ded0c494b428df4791c909f1f174dd21b8ed6a57cf5ddb + computeResources: {} + script: | + if [ -n "${PARAM_BUILDER_IMAGE}" ]; then + echo "WARNING: provided deprecated BUILDER_IMAGE parameter has no effect." + fi + + base_image_name=$(buildah inspect --format '{{ index .ImageAnnotations "org.opencontainers.image.base.name"}}' $IMAGE | cut -f1 -d'@') + base_image_digest=$(buildah inspect --format '{{ index .ImageAnnotations "org.opencontainers.image.base.digest"}}' $IMAGE) + container=$(buildah from --pull-never $IMAGE) + buildah copy $container sbom-cyclonedx.json sbom-purl.json /root/buildinfo/content_manifests/ + buildah config -a org.opencontainers.image.base.name=${base_image_name} -a org.opencontainers.image.base.digest=${base_image_digest} $container + buildah commit $container $IMAGE + + status=-1 + max_run=5 + sleep_sec=10 + for run in $(seq 1 $max_run); do + status=0 + [ "$run" -gt 1 ] && sleep $sleep_sec + echo "Pushing sbom image to registry" + buildah push \ + --tls-verify=$TLSVERIFY \ + --digestfile $(workspaces.source.path)/image-digest $IMAGE \ + docker://$IMAGE && break || status=$? + done + if [ "$status" -ne 0 ]; then + echo "Failed to push sbom image to registry after ${max_run} tries" + exit 1 + fi + + cat "$(workspaces.source.path)"/image-digest | tee $(results.IMAGE_DIGEST.path) + echo -n "$IMAGE" | tee $(results.IMAGE_URL.path) + + securityContext: + runAsUser: 0 + capabilities: + add: + - SETFCAP + volumeMounts: + - mountPath: /var/lib/containers + name: varlibcontainers + workingDir: $(workspaces.source.path) + + - name: upload-sbom + image: quay.io/redhat-appstudio/cosign:v2.1.1@sha256:c883d6f8d39148f2cea71bff4622d196d89df3e510f36c140c097b932f0dd5d5 + args: + - attach + - sbom + - --sbom + - sbom-cyclonedx.json + - --type + - cyclonedx + - $(params.IMAGE) + workingDir: $(workspaces.source.path) + + volumes: + - name: varlibcontainers + emptyDir: {} + - name: etc-pki-entitlement + secret: + secretName: $(params.ENTITLEMENT_SECRET) + optional: true + workspaces: + - name: source + description: Workspace containing the source code to build. diff --git a/ta-generator/golden/buildah/recipe.yaml b/ta-generator/golden/buildah/recipe.yaml new file mode 100644 index 0000000000..aef4e83882 --- /dev/null +++ b/ta-generator/golden/buildah/recipe.yaml @@ -0,0 +1,17 @@ +--- +removeParams: + - BUILDER_IMAGE +add: + - use-source + - use-cachi2 +removeWorkspaces: + - source +replacements: + workspaces.source.path: /var/workdir +regexReplacements: + "/workspace(/.*)": /var/workdir$1 +description: |- + Buildah task builds source code into a container image and pushes the image into container registry using buildah tool. + In addition it generates a SBOM file, injects the SBOM file into final container image and pushes the SBOM file as separate image using cosign tool. + When [Java dependency rebuild](https://redhat-appstudio.github.io/docs.stonesoup.io/Documentation/main/cli/proc_enabled_java_dependencies.html) is enabled it triggers rebuilds of Java artifacts. + When prefetch-dependencies task was activated it is using its artifacts to run build in hermetic environment. diff --git a/ta-generator/golden/buildah/ta.yaml b/ta-generator/golden/buildah/ta.yaml new file mode 100644 index 0000000000..ca29e30f1d --- /dev/null +++ b/ta-generator/golden/buildah/ta.yaml @@ -0,0 +1,461 @@ +--- +apiVersion: tekton.dev/v1 +kind: Task +metadata: + labels: + app.kubernetes.io/version: "0.1" + build.appstudio.redhat.com/build_type: "docker" + annotations: + tekton.dev/pipelines.minVersion: "0.12.1" + tekton.dev/tags: "image-build, appstudio, hacbs" + name: buildah-oci-ta +spec: + description: |- + Buildah task builds source code into a container image and pushes the image into container registry using buildah tool. + In addition it generates a SBOM file, injects the SBOM file into final container image and pushes the SBOM file as separate image using cosign tool. + When [Java dependency rebuild](https://redhat-appstudio.github.io/docs.stonesoup.io/Documentation/main/cli/proc_enabled_java_dependencies.html) is enabled it triggers rebuilds of Java artifacts. + When prefetch-dependencies task was activated it is using its artifacts to run build in hermetic environment. + params: + - description: Reference of the image buildah will produce. + name: IMAGE + type: string + - description: The Trusted Artifact URI pointing to the artifact with the application source code. + name: SOURCE_ARTIFACT + type: string + - description: The Trusted Artifact URI pointing to the artifact with the prefetched dependencies. + name: CACHI2_ARTIFACT + type: string + default: "" + - default: ./Dockerfile + description: Path to the Dockerfile to build. + name: DOCKERFILE + type: string + - default: . + description: Path to the directory to use as context. + name: CONTEXT + type: string + - default: "true" + description: Verify the TLS on the registry endpoint (for push/pull to a non-TLS registry) + name: TLSVERIFY + type: string + - description: unused, should be removed in next task version + name: DOCKER_AUTH + type: string + default: "" + - default: "false" + description: Determines if build will be executed without network access. + name: HERMETIC + type: string + - default: "" + description: In case it is not empty, the prefetched content should be made available to the build. + name: PREFETCH_INPUT + type: string + - default: "" + description: Delete image tag after specified time. Empty means to keep the image tag. Time values could be something like 1h, 2d, 3w for hours, days, and weeks, respectively. + name: IMAGE_EXPIRES_AFTER + type: string + - name: COMMIT_SHA + description: The image is built from this commit. + type: string + default: "" + - name: YUM_REPOS_D_SRC + description: Path in the git repository in which yum repository files are stored + default: repos.d + - name: YUM_REPOS_D_FETCHED + description: Path in source workspace where dynamically-fetched repos are present + default: fetched.repos.d + - name: YUM_REPOS_D_TARGET + description: Target path on the container in which yum repository files should be made available + default: /etc/yum.repos.d + - name: TARGET_STAGE + description: Target stage in Dockerfile to build. If not specified, the Dockerfile is processed entirely to (and including) its last stage. + type: string + default: "" + - name: ENTITLEMENT_SECRET + description: Name of secret which contains the entitlement certificates + type: string + default: "etc-pki-entitlement" + - name: BUILD_ARGS + description: Array of --build-arg values ("arg=value" strings) + type: array + default: [] + - name: BUILD_ARGS_FILE + description: Path to a file with build arguments, see https://www.mankier.com/1/buildah-build#--build-arg-file + type: string + default: "" + + results: + - description: Digest of the image just built + name: IMAGE_DIGEST + - description: Image repository where the built image was pushed + name: IMAGE_URL + - description: Digests of the base images used for build + name: BASE_IMAGES_DIGESTS + - name: SBOM_JAVA_COMPONENTS_COUNT + description: The counting of Java components by publisher in JSON format + type: string + - name: JAVA_COMMUNITY_DEPENDENCIES + description: The Java dependencies that came from community sources such as Maven central. + stepTemplate: + env: + - name: BUILDAH_FORMAT + value: oci + - name: STORAGE_DRIVER + value: vfs + - name: HERMETIC + value: $(params.HERMETIC) + - name: CONTEXT + value: $(params.CONTEXT) + - name: DOCKERFILE + value: $(params.DOCKERFILE) + - name: IMAGE + value: $(params.IMAGE) + - name: TLSVERIFY + value: $(params.TLSVERIFY) + - name: IMAGE_EXPIRES_AFTER + value: $(params.IMAGE_EXPIRES_AFTER) + - name: YUM_REPOS_D_SRC + value: $(params.YUM_REPOS_D_SRC) + - name: YUM_REPOS_D_FETCHED + value: $(params.YUM_REPOS_D_FETCHED) + - name: YUM_REPOS_D_TARGET + value: $(params.YUM_REPOS_D_TARGET) + - name: TARGET_STAGE + value: $(params.TARGET_STAGE) + - name: ENTITLEMENT_SECRET + value: $(params.ENTITLEMENT_SECRET) + - name: BUILD_ARGS_FILE + value: $(params.BUILD_ARGS_FILE) + volumeMounts: + - mountPath: /var/workdir + name: workdir + steps: + - image: quay.io/redhat-appstudio/build-trusted-artifacts:latest@sha256:resolved + name: use-trusted-artifact + args: + - use + - $(params.SOURCE_ARTIFACT)=/var/workdir/source + - $(params.CACHI2_ARTIFACT)=/var/workdir/cachi2 + - image: quay.io/redhat-appstudio/buildah:v1.31.0@sha256:34f12c7b72ec2c28f1ded0c494b428df4791c909f1f174dd21b8ed6a57cf5ddb + name: build + computeResources: + limits: + memory: 4Gi + requests: + memory: 512Mi + cpu: 250m + env: + - name: COMMIT_SHA + value: $(params.COMMIT_SHA) + args: + - $(params.BUILD_ARGS[*]) + script: | + SOURCE_CODE_DIR=source + if [ -e "$SOURCE_CODE_DIR/$CONTEXT/$DOCKERFILE" ]; then + dockerfile_path="$(pwd)/$SOURCE_CODE_DIR/$CONTEXT/$DOCKERFILE" + elif [ -e "$SOURCE_CODE_DIR/$DOCKERFILE" ]; then + dockerfile_path="$(pwd)/$SOURCE_CODE_DIR/$DOCKERFILE" + elif echo "$DOCKERFILE" | grep -q "^https\?://"; then + echo "Fetch Dockerfile from $DOCKERFILE" + dockerfile_path=$(mktemp --suffix=-Dockerfile) + http_code=$(curl -s -L -w "%{http_code}" --output "$dockerfile_path" "$DOCKERFILE") + if [ $http_code != 200 ]; then + echo "No Dockerfile is fetched. Server responds $http_code" + exit 1 + fi + http_code=$(curl -s -L -w "%{http_code}" --output "$dockerfile_path.dockerignore.tmp" "$DOCKERFILE.dockerignore") + if [ $http_code = 200 ]; then + echo "Fetched .dockerignore from $DOCKERFILE.dockerignore" + mv "$dockerfile_path.dockerignore.tmp" $SOURCE_CODE_DIR/$CONTEXT/.dockerignore + fi + else + echo "Cannot find Dockerfile $DOCKERFILE" + exit 1 + fi + if [ -n "$JVM_BUILD_WORKSPACE_ARTIFACT_CACHE_PORT_80_TCP_ADDR" ] && grep -q '^\s*RUN \(./\)\?mvn' "$dockerfile_path"; then + sed -i -e "s|^\s*RUN \(\(./\)\?mvn\)\(.*\)|RUN echo \"mirror.defaulthttp://$JVM_BUILD_WORKSPACE_ARTIFACT_CACHE_PORT_80_TCP_ADDR/v1/cache/default/0/*\" > /tmp/settings.yaml; \1 -s /tmp/settings.yaml \3|g" "$dockerfile_path" + touch /var/lib/containers/java + fi + + # Fixing group permission on /var/lib/containers + chown root:root /var/lib/containers + + sed -i 's/^\s*short-name-mode\s*=\s*.*/short-name-mode = "disabled"/' /etc/containers/registries.conf + + # Setting new namespace to run buildah - 2^32-2 + echo 'root:1:4294967294' | tee -a /etc/subuid >> /etc/subgid + + BUILDAH_ARGS=() + + BASE_IMAGES=$(grep -i '^\s*FROM' "$dockerfile_path" | sed 's/--platform=\S*//' | awk '{print $2}') + if [ "${HERMETIC}" == "true" ]; then + BUILDAH_ARGS+=("--pull=never") + UNSHARE_ARGS="--net" + for image in $BASE_IMAGES; do + if [ "${image}" != "scratch" ]; then + unshare -Ufp --keep-caps -r --map-users 1,1,65536 --map-groups 1,1,65536 -- buildah pull $image + fi + done + echo "Build will be executed with network isolation" + fi + + if [ -n "${TARGET_STAGE}" ]; then + BUILDAH_ARGS+=("--target=${TARGET_STAGE}") + fi + + if [ -n "${BUILD_ARGS_FILE}" ]; then + BUILDAH_ARGS+=("--build-arg-file=$(pwd)/$SOURCE_CODE_DIR/${BUILD_ARGS_FILE}") + fi + + for build_arg in "$@"; do + BUILDAH_ARGS+=("--build-arg=$build_arg") + done + + if [ -d "/var/workdir/cachi2" ]; then + cp -r "/var/workdir/cachi2" /tmp/ + chmod -R go+rwX /tmp/cachi2 + VOLUME_MOUNTS="--volume /tmp/cachi2:/cachi2" + sed -i 's|^\s*run |RUN . /cachi2/cachi2.env \&\& \\\n |i' "$dockerfile_path" + echo "Prefetched content will be made available" + + prefetched_repo_for_my_arch="/tmp/cachi2/output/deps/rpm/$(uname -m)/repos.d/cachi2.repo" + if [ -f "$prefetched_repo_for_my_arch" ]; then + echo "Adding $prefetched_repo_for_my_arch to $YUM_REPOS_D_FETCHED" + mkdir -p "$YUM_REPOS_D_FETCHED" + cp --no-clobber "$prefetched_repo_for_my_arch" "$YUM_REPOS_D_FETCHED" + fi + fi + + # if yum repofiles stored in git, copy them to mount point outside the source dir + if [ -d "${SOURCE_CODE_DIR}/${YUM_REPOS_D_SRC}" ]; then + mkdir -p ${YUM_REPOS_D_FETCHED} + cp -r ${SOURCE_CODE_DIR}/${YUM_REPOS_D_SRC}/* ${YUM_REPOS_D_FETCHED} + fi + + # if anything in the repofiles mount point (either fetched or from git), mount it + if [ -d "${YUM_REPOS_D_FETCHED}" ]; then + chmod -R go+rwX ${YUM_REPOS_D_FETCHED} + mount_point=$(realpath ${YUM_REPOS_D_FETCHED}) + VOLUME_MOUNTS="${VOLUME_MOUNTS} --volume ${mount_point}:${YUM_REPOS_D_TARGET}" + fi + + LABELS=( + "--label" "build-date=$(date -u +'%Y-%m-%dT%H:%M:%S')" + "--label" "architecture=$(uname -m)" + "--label" "vcs-type=git" + ) + [ -n "$COMMIT_SHA" ] && LABELS+=("--label" "vcs-ref=$COMMIT_SHA") + [ -n "$IMAGE_EXPIRES_AFTER" ] && LABELS+=("--label" "quay.expires-after=$IMAGE_EXPIRES_AFTER") + + ENTITLEMENT_PATH="/entitlement" + if [ -d "$ENTITLEMENT_PATH" ]; then + cp -r --preserve=mode "$ENTITLEMENT_PATH" /tmp/entitlement + VOLUME_MOUNTS="${VOLUME_MOUNTS} --volume /tmp/entitlement:/etc/pki/entitlement" + echo "Adding the entitlement to the build" + fi + + unshare -Uf $UNSHARE_ARGS --keep-caps -r --map-users 1,1,65536 --map-groups 1,1,65536 -w ${SOURCE_CODE_DIR}/$CONTEXT -- buildah build \ + $VOLUME_MOUNTS \ + "${BUILDAH_ARGS[@]}" \ + "${LABELS[@]}" \ + --tls-verify=$TLSVERIFY --no-cache \ + --ulimit nofile=4096:4096 \ + -f "$dockerfile_path" -t $IMAGE . + + container=$(buildah from --pull-never $IMAGE) + buildah mount $container | tee /var/workdir/container_path + echo $container > /var/workdir/container_name + + # Save the SBOM produced by Cachi2 so it can be merged into the final SBOM later + if [ -d "/var/workdir/cachi2" ]; then + cp /tmp/cachi2/output/bom.json ./sbom-cachi2.json + fi + + # Expose base image digests + for image in $BASE_IMAGES; do + if [ "${image}" != "scratch" ]; then + buildah images --format '{{ .Name }}:{{ .Tag }}@{{ .Digest }}' --filter reference="$image" >> $(results.BASE_IMAGES_DIGESTS.path) + fi + done + + # Needed to generate base images SBOM + echo "$BASE_IMAGES" > /var/workdir/base_images_from_dockerfile + + securityContext: + capabilities: + add: + - SETFCAP + volumeMounts: + - mountPath: /var/lib/containers + name: varlibcontainers + - mountPath: "/entitlement" + name: etc-pki-entitlement + workingDir: /var/workdir + + - name: sbom-syft-generate + image: quay.io/redhat-appstudio/syft:v0.105.1@sha256:1910b829997650c696881e5fc2fc654ddf3184c27edb1b2024e9cb2ba51ac431 + workingDir: /var/workdir/source + script: | + echo "Running syft on the source directory" + syft dir:/var/workdir/source --output cyclonedx-json=/var/workdir/sbom-source.json + find $(cat /var/workdir/container_path) -xtype l -delete + echo "Running syft on the image filesystem" + syft dir:$(cat /var/workdir/container_path) --output cyclonedx-json=/var/workdir/sbom-image.json + volumeMounts: + - mountPath: /var/lib/containers + name: varlibcontainers + - name: analyse-dependencies-java-sbom + image: quay.io/redhat-appstudio/hacbs-jvm-build-request-processor:127ee0c223a2b56a9bd20a6f2eaeed3bd6015f77 + script: | + if [ -f /var/lib/containers/java ]; then + /opt/jboss/container/java/run/run-java.sh analyse-dependencies path $(cat /var/workdir/container_path) -s /var/workdir/sbom-image.json --task-run-name $(context.taskRun.name) --publishers $(results.SBOM_JAVA_COMPONENTS_COUNT.path) + sed -i 's/^/ /' $(results.SBOM_JAVA_COMPONENTS_COUNT.path) # Workaround for SRVKP-2875 + else + touch $(results.JAVA_COMMUNITY_DEPENDENCIES.path) + fi + volumeMounts: + - mountPath: /var/lib/containers + name: varlibcontainers + securityContext: + runAsUser: 0 + + - name: merge-syft-sboms + image: registry.access.redhat.com/ubi9/python-39:1-172.1712567222@sha256:c96f839e927c52990143df4efb2872946fcd5de9e1ed2014947bb2cf3084c27a + script: | + #!/bin/python3 + import json + + # load SBOMs + with open("./sbom-image.json") as f: + image_sbom = json.load(f) + + with open("./sbom-source.json") as f: + source_sbom = json.load(f) + + # fetch unique components from available SBOMs + def get_identifier(component): + return component["name"] + '@' + component.get("version", "") + + image_sbom_components = image_sbom.get("components", []) + existing_components = [get_identifier(component) for component in image_sbom_components] + + source_sbom_components = source_sbom.get("components", []) + for component in source_sbom_components: + if get_identifier(component) not in existing_components: + image_sbom_components.append(component) + existing_components.append(get_identifier(component)) + + image_sbom_components.sort(key=lambda c: get_identifier(c)) + + # write the CycloneDX unified SBOM + with open("./sbom-cyclonedx.json", "w") as f: + json.dump(image_sbom, f, indent=4) + workingDir: /var/workdir + securityContext: + runAsUser: 0 + + - name: merge-cachi2-sbom + image: quay.io/redhat-appstudio/cachi2:0.7.0@sha256:1fc772aa3636fd0b43d62120d832e5913843e028e8cac42814b487c3a0a32bd8 + script: | + if [ -d "/var/workdir/cachi2" ]; then + echo "Merging contents of sbom-cachi2.json into sbom-cyclonedx.json" + /src/utils/merge_syft_sbom.py sbom-cachi2.json sbom-cyclonedx.json > sbom-temp.json + mv sbom-temp.json sbom-cyclonedx.json + else + echo "Skipping step since no Cachi2 SBOM was produced" + fi + workingDir: /var/workdir + securityContext: + runAsUser: 0 + + - name: create-purl-sbom + image: registry.access.redhat.com/ubi9/python-39:1-172.1712567222@sha256:c96f839e927c52990143df4efb2872946fcd5de9e1ed2014947bb2cf3084c27a + script: | + #!/bin/python3 + import json + + with open("./sbom-cyclonedx.json") as f: + cyclonedx_sbom = json.load(f) + + purls = [{"purl": component["purl"]} for component in cyclonedx_sbom.get("components", []) if "purl" in component] + purl_content = {"image_contents": {"dependencies": purls}} + + with open("sbom-purl.json", "w") as output_file: + json.dump(purl_content, output_file, indent=4) + workingDir: /var/workdir + securityContext: + runAsUser: 0 + + - name: create-base-images-sbom + image: quay.io/redhat-appstudio/base-images-sbom-script@sha256:667669e3def018f9dbb8eaf8868887a40bc07842221e9a98f6787edcff021840 + env: + - name: BASE_IMAGES_DIGESTS_PATH + value: $(results.BASE_IMAGES_DIGESTS.path) + script: | + python3 /app/base_images_sbom_script.py --sbom=sbom-cyclonedx.json --base-images-from-dockerfile=base_images_from_dockerfile --base-images-digests=$BASE_IMAGES_DIGESTS_PATH + workingDir: /var/workdir + securityContext: + runAsUser: 0 + + - name: inject-sbom-and-push + image: quay.io/redhat-appstudio/buildah:v1.31.0@sha256:34f12c7b72ec2c28f1ded0c494b428df4791c909f1f174dd21b8ed6a57cf5ddb + computeResources: {} + script: | + base_image_name=$(buildah inspect --format '{{ index .ImageAnnotations "org.opencontainers.image.base.name"}}' $IMAGE | cut -f1 -d'@') + base_image_digest=$(buildah inspect --format '{{ index .ImageAnnotations "org.opencontainers.image.base.digest"}}' $IMAGE) + container=$(buildah from --pull-never $IMAGE) + buildah copy $container sbom-cyclonedx.json sbom-purl.json /root/buildinfo/content_manifests/ + buildah config -a org.opencontainers.image.base.name=${base_image_name} -a org.opencontainers.image.base.digest=${base_image_digest} $container + buildah commit $container $IMAGE + + status=-1 + max_run=5 + sleep_sec=10 + for run in $(seq 1 $max_run); do + status=0 + [ "$run" -gt 1 ] && sleep $sleep_sec + echo "Pushing sbom image to registry" + buildah push \ + --tls-verify=$TLSVERIFY \ + --digestfile /var/workdir/image-digest $IMAGE \ + docker://$IMAGE && break || status=$? + done + if [ "$status" -ne 0 ]; then + echo "Failed to push sbom image to registry after ${max_run} tries" + exit 1 + fi + + cat "/var/workdir"/image-digest | tee $(results.IMAGE_DIGEST.path) + echo -n "$IMAGE" | tee $(results.IMAGE_URL.path) + + securityContext: + runAsUser: 0 + capabilities: + add: + - SETFCAP + volumeMounts: + - mountPath: /var/lib/containers + name: varlibcontainers + workingDir: /var/workdir + + - name: upload-sbom + image: quay.io/redhat-appstudio/cosign:v2.1.1@sha256:c883d6f8d39148f2cea71bff4622d196d89df3e510f36c140c097b932f0dd5d5 + args: + - attach + - sbom + - --sbom + - sbom-cyclonedx.json + - --type + - cyclonedx + - $(params.IMAGE) + workingDir: /var/workdir + + volumes: + - name: varlibcontainers + emptyDir: {} + - name: workdir + emptyDir: {} + - name: etc-pki-entitlement + secret: + secretName: $(params.ENTITLEMENT_SECRET) + optional: true diff --git a/ta-generator/golden/git-clone/base.yaml b/ta-generator/golden/git-clone/base.yaml new file mode 100644 index 0000000000..70817c33bc --- /dev/null +++ b/ta-generator/golden/git-clone/base.yaml @@ -0,0 +1,313 @@ +--- +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: git-clone + annotations: + tekton.dev/categories: Git + tekton.dev/displayName: git clone + tekton.dev/pipelines.minVersion: 0.21.0 + tekton.dev/platforms: linux/amd64,linux/s390x,linux/ppc64le,linux/arm64 + tekton.dev/tags: git + labels: + app.kubernetes.io/version: "0.1" +spec: + description: |- + The git-clone Task will clone a repo from the provided url into the output Workspace. By default the repo will be cloned into the root of your Workspace. + params: + - description: Repository URL to clone from. + name: url + type: string + - default: "" + description: Revision to checkout. (branch, tag, sha, ref, etc...) + name: revision + type: string + - default: "" + description: Refspec to fetch before checking out revision. + name: refspec + type: string + - default: "true" + description: Initialize and fetch git submodules. + name: submodules + type: string + - default: "1" + description: Perform a shallow clone, fetching only the most recent + N commits. + name: depth + type: string + - default: "true" + description: Set the `http.sslVerify` global git config. Setting + this to `false` is not advised unless you are sure that you trust + your git remote. + name: sslVerify + type: string + - default: "source" + description: Subdirectory inside the `output` Workspace to clone + the repo into. + name: subdirectory + type: string + - default: "" + description: Define the directory patterns to match or exclude when + performing a sparse checkout. + name: sparseCheckoutDirectories + type: string + - default: "true" + description: Clean out the contents of the destination directory + if it already exists before cloning. + name: deleteExisting + type: string + - default: "" + description: HTTP proxy server for non-SSL requests. + name: httpProxy + type: string + - default: "" + description: HTTPS proxy server for SSL requests. + name: httpsProxy + type: string + - default: "" + description: Opt out of proxying HTTP/HTTPS requests. + name: noProxy + type: string + - default: "false" + description: Log the commands that are executed during `git-clone`'s + operation. + name: verbose + type: string + - default: "" + description: Deprecated. Has no effect. Will be removed in the future. + name: gitInitImage + type: string + - default: /tekton/home + description: | + Absolute path to the user's home directory. Set this explicitly if you are running the image as a non-root user. + name: userHome + type: string + - default: "true" + description: | + Check symlinks in the repo. If they're pointing outside of the repo, the build will fail. + name: enableSymlinkCheck + type: string + - default: "false" + description: Fetch all tags for the repo. + name: fetchTags + type: string + - name: caTrustConfigMapName + type: string + description: The name of the ConfigMap to read CA bundle data from. + default: trusted-ca + - name: caTrustConfigMapKey + type: string + description: The name of the key in the ConfigMap that contains + the CA bundle data. + default: ca-bundle.crt + results: + - description: The precise commit SHA that was fetched by this Task. + name: commit + - description: The precise URL that was fetched by this Task. + name: url + volumes: + - name: trusted-ca + configMap: + name: $(params.caTrustConfigMapName) + items: + - key: $(params.caTrustConfigMapKey) + path: ca-bundle.crt + optional: true + workspaces: + - description: The git repo will be cloned onto the volume backing + this Workspace. + name: output + - description: | + A .ssh directory with private key, known_hosts, config, etc. Copied to + the user's home before git commands are executed. Used to authenticate + with the git remote when performing the clone. Binding a Secret to this + Workspace is strongly recommended over other volume types. + name: ssh-directory + optional: true + - description: | + A Workspace containing a .gitconfig and .git-credentials file or username and password. + These will be copied to the user's home before any git commands are run. Any + other files in this Workspace are ignored. It is strongly recommended + to use ssh-directory over basic-auth whenever possible and to bind a + Secret to this Workspace over other volume types. + name: basic-auth + optional: true + steps: + - name: clone + image: registry.redhat.io/openshift-pipelines/pipelines-git-init-rhel8:v1.8.2-8@sha256:a538c423e7a11aae6ae582a411fdb090936458075f99af4ce5add038bb6983e8 + volumeMounts: + - name: trusted-ca + mountPath: /mnt/trusted-ca + readOnly: true + env: + - name: HOME + value: $(params.userHome) + - name: PARAM_URL + value: $(params.url) + - name: PARAM_REVISION + value: $(params.revision) + - name: PARAM_REFSPEC + value: $(params.refspec) + - name: PARAM_SUBMODULES + value: $(params.submodules) + - name: PARAM_DEPTH + value: $(params.depth) + - name: PARAM_SSL_VERIFY + value: $(params.sslVerify) + - name: PARAM_SUBDIRECTORY + value: $(params.subdirectory) + - name: PARAM_DELETE_EXISTING + value: $(params.deleteExisting) + - name: PARAM_HTTP_PROXY + value: $(params.httpProxy) + - name: PARAM_HTTPS_PROXY + value: $(params.httpsProxy) + - name: PARAM_NO_PROXY + value: $(params.noProxy) + - name: PARAM_VERBOSE + value: $(params.verbose) + - name: PARAM_SPARSE_CHECKOUT_DIRECTORIES + value: $(params.sparseCheckoutDirectories) + - name: PARAM_USER_HOME + value: $(params.userHome) + - name: PARAM_FETCH_TAGS + value: $(params.fetchTags) + - name: PARAM_GIT_INIT_IMAGE + value: $(params.gitInitImage) + - name: WORKSPACE_OUTPUT_PATH + value: $(workspaces.output.path) + - name: WORKSPACE_SSH_DIRECTORY_BOUND + value: $(workspaces.ssh-directory.bound) + - name: WORKSPACE_SSH_DIRECTORY_PATH + value: $(workspaces.ssh-directory.path) + - name: WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND + value: $(workspaces.basic-auth.bound) + - name: WORKSPACE_BASIC_AUTH_DIRECTORY_PATH + value: $(workspaces.basic-auth.path) + script: | + #!/usr/bin/env sh + set -eu + + if [ "${PARAM_VERBOSE}" = "true" ]; then + set -x + fi + + if [ -n "${PARAM_GIT_INIT_IMAGE}" ]; then + echo "WARNING: provided deprecated gitInitImage parameter has no effect." + fi + + if [ "${WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND}" = "true" ]; then + if [ -f "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.git-credentials" ] && [ -f "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.gitconfig" ]; then + cp "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.git-credentials" "${PARAM_USER_HOME}/.git-credentials" + cp "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.gitconfig" "${PARAM_USER_HOME}/.gitconfig" + # Compatibility with kubernetes.io/basic-auth secrets + elif [ -f "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/username" ] && [ -f "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/password" ]; then + HOSTNAME=$(echo $PARAM_URL | awk -F/ '{print $3}') + echo "https://$(cat ${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/username):$(cat ${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/password)@$HOSTNAME" >"${PARAM_USER_HOME}/.git-credentials" + echo -e "[credential \"https://$HOSTNAME\"]\n helper = store" >"${PARAM_USER_HOME}/.gitconfig" + else + echo "Unknown basic-auth workspace format" + exit 1 + fi + chmod 400 "${PARAM_USER_HOME}/.git-credentials" + chmod 400 "${PARAM_USER_HOME}/.gitconfig" + fi + + # Should be called after the gitconfig is copied from the repository secret + ca_bundle=/mnt/trusted-ca/ca-bundle.crt + if [ -f "$ca_bundle" ]; then + echo "INFO: Using mounted CA bundle: $ca_bundle" + git config --global http.sslCAInfo "$ca_bundle" + fi + + if [ "${WORKSPACE_SSH_DIRECTORY_BOUND}" = "true" ]; then + cp -R "${WORKSPACE_SSH_DIRECTORY_PATH}" "${PARAM_USER_HOME}"/.ssh + chmod 700 "${PARAM_USER_HOME}"/.ssh + chmod -R 400 "${PARAM_USER_HOME}"/.ssh/* + fi + + CHECKOUT_DIR="${WORKSPACE_OUTPUT_PATH}/${PARAM_SUBDIRECTORY}" + + cleandir() { + # Delete any existing contents of the repo directory if it exists. + # + # We don't just "rm -rf ${CHECKOUT_DIR}" because ${CHECKOUT_DIR} might be "/" + # or the root of a mounted volume. + if [ -d "${CHECKOUT_DIR}" ]; then + # Delete non-hidden files and directories + rm -rf "${CHECKOUT_DIR:?}"/* + # Delete files and directories starting with . but excluding .. + rm -rf "${CHECKOUT_DIR}"/.[!.]* + # Delete files and directories starting with .. plus any other character + rm -rf "${CHECKOUT_DIR}"/..?* + fi + } + + if [ "${PARAM_DELETE_EXISTING}" = "true" ]; then + cleandir + fi + + test -z "${PARAM_HTTP_PROXY}" || export HTTP_PROXY="${PARAM_HTTP_PROXY}" + test -z "${PARAM_HTTPS_PROXY}" || export HTTPS_PROXY="${PARAM_HTTPS_PROXY}" + test -z "${PARAM_NO_PROXY}" || export NO_PROXY="${PARAM_NO_PROXY}" + + /ko-app/git-init \ + -url="${PARAM_URL}" \ + -revision="${PARAM_REVISION}" \ + -refspec="${PARAM_REFSPEC}" \ + -path="${CHECKOUT_DIR}" \ + -sslVerify="${PARAM_SSL_VERIFY}" \ + -submodules="${PARAM_SUBMODULES}" \ + -depth="${PARAM_DEPTH}" \ + -sparseCheckoutDirectories="${PARAM_SPARSE_CHECKOUT_DIRECTORIES}" + cd "${CHECKOUT_DIR}" + RESULT_SHA="$(git rev-parse HEAD)" + EXIT_CODE="$?" + if [ "${EXIT_CODE}" != 0 ]; then + exit "${EXIT_CODE}" + fi + printf "%s" "${RESULT_SHA}" >"$(results.commit.path)" + printf "%s" "${PARAM_URL}" >"$(results.url.path)" + + if [ "${PARAM_FETCH_TAGS}" = "true" ]; then + echo "Fetching tags" + git fetch --tags + fi + computeResources: {} + securityContext: + runAsUser: 0 + - name: symlink-check + image: registry.redhat.io/ubi9:9.2-696@sha256:089bd3b82a78ac45c0eed231bb58bfb43bfcd0560d9bba240fc6355502c92976 + # per https://kubernetes.io/docs/concepts/containers/images/#imagepullpolicy-defaulting + # the cluster will set imagePullPolicy to IfNotPresent + env: + - name: PARAM_ENABLE_SYMLINK_CHECK + value: $(params.enableSymlinkCheck) + - name: PARAM_SUBDIRECTORY + value: $(params.subdirectory) + - name: WORKSPACE_OUTPUT_PATH + value: $(workspaces.output.path) + script: | + #!/usr/bin/env bash + set -euo pipefail + + CHECKOUT_DIR="${WORKSPACE_OUTPUT_PATH}/${PARAM_SUBDIRECTORY}" + check_symlinks() { + FOUND_SYMLINK_POINTING_OUTSIDE_OF_REPO=false + while read symlink; do + target=$(readlink -f "$symlink") + if ! [[ "$target" =~ ^$CHECKOUT_DIR ]]; then + echo "The cloned repository contains symlink pointing outside of the cloned repository: $symlink" + FOUND_SYMLINK_POINTING_OUTSIDE_OF_REPO=true + fi + done < <(find $CHECKOUT_DIR -type l -print) + if [ "$FOUND_SYMLINK_POINTING_OUTSIDE_OF_REPO" = true ]; then + return 1 + fi + } + + if [ "${PARAM_ENABLE_SYMLINK_CHECK}" = "true" ]; then + echo "Running symlink check" + check_symlinks + fi + computeResources: {} diff --git a/ta-generator/golden/git-clone/recipe.yaml b/ta-generator/golden/git-clone/recipe.yaml new file mode 100644 index 0000000000..7f5ed5b512 --- /dev/null +++ b/ta-generator/golden/git-clone/recipe.yaml @@ -0,0 +1,14 @@ +--- +removeParams: + - gitInitImage + - deleteExisting + - subdirectory +addEnvironment: + - name: CHECKOUT_DIR + value: /var/workdir/source +add: + - create-source +removeWorkspaces: + - output +description: The git-clone-oci-ta Task will clone a repo from the provided url and store it as a trusted + artifact in the provided OCI repository. diff --git a/ta-generator/golden/git-clone/ta.yaml b/ta-generator/golden/git-clone/ta.yaml new file mode 100644 index 0000000000..40c71aa88e --- /dev/null +++ b/ta-generator/golden/git-clone/ta.yaml @@ -0,0 +1,292 @@ +--- +apiVersion: tekton.dev/v1 +kind: Task +metadata: + labels: + app.kubernetes.io/version: "0.1" + annotations: + tekton.dev/categories: Git + tekton.dev/displayName: git clone oci trusted artifacts + tekton.dev/pipelines.minVersion: 0.21.0 + tekton.dev/platforms: linux/amd64,linux/s390x,linux/ppc64le,linux/arm64 + tekton.dev/tags: git + name: git-clone-oci-ta +spec: + description: >- + The git-clone-oci-ta Task will clone a repo from the provided url and store it as a trusted + artifact in the provided OCI repository. + params: + - description: Repository URL to clone from. + name: url + type: string + - default: "" + description: Revision to checkout. (branch, tag, sha, ref, etc...) + name: revision + type: string + - default: "" + description: Refspec to fetch before checking out revision. + name: refspec + type: string + - default: "true" + description: Initialize and fetch git submodules. + name: submodules + type: string + - default: "1" + description: Perform a shallow clone, fetching only the most recent N commits. + name: depth + type: string + - default: "true" + description: Set the `http.sslVerify` global git config. Setting this to `false` is not advised unless you are sure that you trust your git remote. + name: sslVerify + type: string + - default: "" + description: Define the directory patterns to match or exclude when performing a sparse checkout. + name: sparseCheckoutDirectories + type: string + - default: "" + description: HTTP proxy server for non-SSL requests. + name: httpProxy + type: string + - default: "" + description: HTTPS proxy server for SSL requests. + name: httpsProxy + type: string + - default: "" + description: Opt out of proxying HTTP/HTTPS requests. + name: noProxy + type: string + - default: "false" + description: Log the commands that are executed during `git-clone`'s operation. + name: verbose + type: string + - default: /tekton/home + description: | + Absolute path to the user's home directory. Set this explicitly if you are running the image as a non-root user. + name: userHome + type: string + - default: "true" + description: | + Check symlinks in the repo. If they're pointing outside of the repo, the build will fail. + name: enableSymlinkCheck + type: string + - default: "false" + description: Fetch all tags for the repo. + name: fetchTags + type: string + - name: caTrustConfigMapName + type: string + description: The name of the ConfigMap to read CA bundle data from. + default: trusted-ca + - name: caTrustConfigMapKey + type: string + description: The name of the key in the ConfigMap that contains the CA bundle data. + default: ca-bundle.crt + - name: ociStorage + type: string + description: The OCI repository where the Trusted Artifacts are stored. + - name: ociArtifactExpiresAfter + type: string + description: >- + Expiration date for the trusted artifacts created in the OCI repository. An empty string means + the artifacts do not expire. + default: "" + + results: + - description: The precise commit SHA that was fetched by this Task. + name: commit + - description: The precise URL that was fetched by this Task. + name: url + - description: The Trusted Artifact URI pointing to the artifact with the application source code. + name: SOURCE_ARTIFACT + type: string + steps: + - name: clone + env: + - name: HOME + value: $(params.userHome) + - name: PARAM_URL + value: $(params.url) + - name: PARAM_REVISION + value: $(params.revision) + - name: PARAM_REFSPEC + value: $(params.refspec) + - name: PARAM_SUBMODULES + value: $(params.submodules) + - name: PARAM_DEPTH + value: $(params.depth) + - name: PARAM_SSL_VERIFY + value: $(params.sslVerify) + - name: PARAM_HTTP_PROXY + value: $(params.httpProxy) + - name: PARAM_HTTPS_PROXY + value: $(params.httpsProxy) + - name: PARAM_NO_PROXY + value: $(params.noProxy) + - name: PARAM_VERBOSE + value: $(params.verbose) + - name: PARAM_SPARSE_CHECKOUT_DIRECTORIES + value: $(params.sparseCheckoutDirectories) + - name: PARAM_USER_HOME + value: $(params.userHome) + - name: PARAM_FETCH_TAGS + value: $(params.fetchTags) + - name: WORKSPACE_SSH_DIRECTORY_BOUND + value: $(workspaces.ssh-directory.bound) + - name: WORKSPACE_SSH_DIRECTORY_PATH + value: $(workspaces.ssh-directory.path) + - name: WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND + value: $(workspaces.basic-auth.bound) + - name: WORKSPACE_BASIC_AUTH_DIRECTORY_PATH + value: $(workspaces.basic-auth.path) + - name: CHECKOUT_DIR + value: /var/workdir/source + image: registry.redhat.io/openshift-pipelines/pipelines-git-init-rhel8:v1.8.2-8@sha256:a538c423e7a11aae6ae582a411fdb090936458075f99af4ce5add038bb6983e8 + computeResources: {} + securityContext: + runAsUser: 0 + volumeMounts: + - name: trusted-ca + mountPath: /mnt/trusted-ca + readOnly: true + - name: workdir + mountPath: /var/workdir + script: | + #!/usr/bin/env sh + set -eu + + if [ "${PARAM_VERBOSE}" = "true" ] ; then + set -x + fi + + if [ "${WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND}" = "true" ] ; then + if [ -f "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.git-credentials" ] && [ -f "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.gitconfig" ]; then + cp "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.git-credentials" "${PARAM_USER_HOME}/.git-credentials" + cp "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.gitconfig" "${PARAM_USER_HOME}/.gitconfig" + # Compatibility with kubernetes.io/basic-auth secrets + elif [ -f "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/username" ] && [ -f "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/password" ]; then + HOSTNAME=$(echo $PARAM_URL | awk -F/ '{print $3}') + echo "https://$(cat ${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/username):$(cat ${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/password)@$HOSTNAME" > "${PARAM_USER_HOME}/.git-credentials" + echo -e "[credential \"https://$HOSTNAME\"]\n helper = store" > "${PARAM_USER_HOME}/.gitconfig" + else + echo "Unknown basic-auth workspace format" + exit 1 + fi + chmod 400 "${PARAM_USER_HOME}/.git-credentials" + chmod 400 "${PARAM_USER_HOME}/.gitconfig" + fi + + # Should be called after the gitconfig is copied from the repository secret + ca_bundle=/mnt/trusted-ca/ca-bundle.crt + if [ -f "$ca_bundle" ]; then + echo "INFO: Using mounted CA bundle: $ca_bundle" + git config --global http.sslCAInfo "$ca_bundle" + fi + + if [ "${WORKSPACE_SSH_DIRECTORY_BOUND}" = "true" ] ; then + cp -R "${WORKSPACE_SSH_DIRECTORY_PATH}" "${PARAM_USER_HOME}"/.ssh + chmod 700 "${PARAM_USER_HOME}"/.ssh + chmod -R 400 "${PARAM_USER_HOME}"/.ssh/* + fi + + test -z "${PARAM_HTTP_PROXY}" || export HTTP_PROXY="${PARAM_HTTP_PROXY}" + test -z "${PARAM_HTTPS_PROXY}" || export HTTPS_PROXY="${PARAM_HTTPS_PROXY}" + test -z "${PARAM_NO_PROXY}" || export NO_PROXY="${PARAM_NO_PROXY}" + + /ko-app/git-init \ + -url="${PARAM_URL}" \ + -revision="${PARAM_REVISION}" \ + -refspec="${PARAM_REFSPEC}" \ + -path="${CHECKOUT_DIR}" \ + -sslVerify="${PARAM_SSL_VERIFY}" \ + -submodules="${PARAM_SUBMODULES}" \ + -depth="${PARAM_DEPTH}" \ + -sparseCheckoutDirectories="${PARAM_SPARSE_CHECKOUT_DIRECTORIES}" + cd "${CHECKOUT_DIR}" + RESULT_SHA="$(git rev-parse HEAD)" + EXIT_CODE="$?" + if [ "${EXIT_CODE}" != 0 ] ; then + exit "${EXIT_CODE}" + fi + printf "%s" "${RESULT_SHA}" > "$(results.commit.path)" + printf "%s" "${PARAM_URL}" > "$(results.url.path)" + + if [ "${PARAM_FETCH_TAGS}" = "true" ] ; then + echo "Fetching tags" + git fetch --tags + fi + + - name: symlink-check + image: registry.redhat.io/ubi9:9.2-696@sha256:089bd3b82a78ac45c0eed231bb58bfb43bfcd0560d9bba240fc6355502c92976 + env: + - name: PARAM_ENABLE_SYMLINK_CHECK + value: $(params.enableSymlinkCheck) + - name: CHECKOUT_DIR + value: /var/workdir/source + volumeMounts: + - name: workdir + mountPath: /var/workdir + computeResources: {} + script: | + #!/usr/bin/env bash + set -euo pipefail + + check_symlinks() { + FOUND_SYMLINK_POINTING_OUTSIDE_OF_REPO=false + while read symlink + do + target=$(readlink -f "$symlink") + if ! [[ "$target" =~ ^$CHECKOUT_DIR ]]; then + echo "The cloned repository contains symlink pointing outside of the cloned repository: $symlink" + FOUND_SYMLINK_POINTING_OUTSIDE_OF_REPO=true + fi + done < <(find $CHECKOUT_DIR -type l -print) + if [ "$FOUND_SYMLINK_POINTING_OUTSIDE_OF_REPO" = true ] ; then + return 1 + fi + } + + if [ "${PARAM_ENABLE_SYMLINK_CHECK}" = "true" ] ; then + echo "Running symlink check" + check_symlinks + fi + + - name: create-trusted-artifact + image: quay.io/redhat-appstudio/build-trusted-artifacts:latest@sha256:existing + env: + - name: IMAGE_EXPIRES_AFTER + value: $(params.ociArtifactExpiresAfter) + volumeMounts: + - name: workdir + mountPath: /var/workdir + args: + - create + - --store + - $(params.ociStorage) + - $(results.SOURCE_ARTIFACT.path)=/var/workdir/source + + workspaces: + - description: | + A .ssh directory with private key, known_hosts, config, etc. Copied to + the user's home before git commands are executed. Used to authenticate + with the git remote when performing the clone. Binding a Secret to this + Workspace is strongly recommended over other volume types. + name: ssh-directory + optional: true + - description: | + A Workspace containing a .gitconfig and .git-credentials file or username and password. + These will be copied to the user's home before any git commands are run. Any + other files in this Workspace are ignored. It is strongly recommended + to use ssh-directory over basic-auth whenever possible and to bind a + Secret to this Workspace over other volume types. + name: basic-auth + optional: true + volumes: + - name: workdir + emptyDir: {} + - name: trusted-ca + configMap: + name: $(params.caTrustConfigMapName) + items: + - key: $(params.caTrustConfigMapKey) + path: ca-bundle.crt + optional: true diff --git a/ta-generator/golden/prefetch-dependencies/base.yaml b/ta-generator/golden/prefetch-dependencies/base.yaml new file mode 100644 index 0000000000..0161463fb2 --- /dev/null +++ b/ta-generator/golden/prefetch-dependencies/base.yaml @@ -0,0 +1,122 @@ +apiVersion: tekton.dev/v1 +kind: Task +metadata: + labels: + app.kubernetes.io/version: "0.1" + annotations: + tekton.dev/pipelines.minVersion: "0.12.1" + tekton.dev/tags: "image-build, hacbs" + name: prefetch-dependencies +spec: + description: |- + Task that uses Cachi2 to prefetch build dependencies. + See docs at https://github.com/containerbuildsystem/cachi2#basic-usage. + params: + - description: Configures project packages that will have their dependencies prefetched. + name: input + - description: > + Enable in-development package managers. WARNING: the behavior may change at any time without + notice. Use at your own risk. + name: dev-package-managers + default: "false" + - description: Set cachi2 log level (debug, info, warning, error) + name: log-level + default: "info" + - name: caTrustConfigMapName + type: string + description: The name of the ConfigMap to read CA bundle data from. + default: trusted-ca + - name: caTrustConfigMapKey + type: string + description: The name of the key in the ConfigMap that contains the CA bundle data. + default: ca-bundle.crt + steps: + - image: quay.io/redhat-appstudio/cachi2:0.7.0@sha256:1fc772aa3636fd0b43d62120d832e5913843e028e8cac42814b487c3a0a32bd8 + # per https://kubernetes.io/docs/concepts/containers/images/#imagepullpolicy-defaulting + # the cluster will set imagePullPolicy to IfNotPresent + name: prefetch-dependencies + env: + - name: INPUT + value: $(params.input) + - name: DEV_PACKAGE_MANAGERS + value: $(params.dev-package-managers) + - name: LOG_LEVEL + value: $(params.log-level) + - name: WORKSPACE_GIT_AUTH_BOUND + value: $(workspaces.git-basic-auth.bound) + - name: WORKSPACE_GIT_AUTH_PATH + value: $(workspaces.git-basic-auth.path) + volumeMounts: + - name: trusted-ca + mountPath: /mnt/trusted-ca + readOnly: true + script: | + if [ -z "${INPUT}" ] + then + # Confirm input was provided though it's likely the whole task would be skipped if it wasn't + echo "No prefetch will be performed because no input was provided for cachi2 fetch-deps" + exit 0 + fi + + if [ "$DEV_PACKAGE_MANAGERS" = "true" ]; then + dev_pacman_flag=--dev-package-managers + else + dev_pacman_flag="" + fi + + # Copied from https://github.com/konflux-ci/build-definitions/blob/main/task/git-clone/0.1/git-clone.yaml + if [ "${WORKSPACE_GIT_AUTH_BOUND}" = "true" ] ; then + if [ -f "${WORKSPACE_GIT_AUTH_PATH}/.git-credentials" ] && [ -f "${WORKSPACE_GIT_AUTH_PATH}/.gitconfig" ]; then + cp "${WORKSPACE_GIT_AUTH_PATH}/.git-credentials" "${HOME}/.git-credentials" + cp "${WORKSPACE_GIT_AUTH_PATH}/.gitconfig" "${HOME}/.gitconfig" + # Compatibility with kubernetes.io/basic-auth secrets + elif [ -f "${WORKSPACE_GIT_AUTH_PATH}/username" ] && [ -f "${WORKSPACE_GIT_AUTH_PATH}/password" ]; then + HOSTNAME=$(cd "$(workspaces.source.path)/source" && git remote get-url origin | awk -F/ '{print $3}') + echo "https://$(cat ${WORKSPACE_GIT_AUTH_PATH}/username):$(cat ${WORKSPACE_GIT_AUTH_PATH}/password)@$HOSTNAME" > "${HOME}/.git-credentials" + echo -e "[credential \"https://$HOSTNAME\"]\n helper = store" > "${HOME}/.gitconfig" + else + echo "Unknown git-basic-auth workspace format" + exit 1 + fi + chmod 400 "${HOME}/.git-credentials" + chmod 400 "${HOME}/.gitconfig" + fi + + ca_bundle=/mnt/trusted-ca/ca-bundle.crt + if [ -f "$ca_bundle" ]; then + echo "INFO: Using mounted CA bundle: $ca_bundle" + cp -vf $ca_bundle /etc/pki/ca-trust/source/anchors + update-ca-trust + fi + + cachi2 --log-level="$LOG_LEVEL" fetch-deps \ + $dev_pacman_flag \ + --source=$(workspaces.source.path)/source \ + --output=$(workspaces.source.path)/cachi2/output \ + "${INPUT}" + + cachi2 --log-level="$LOG_LEVEL" generate-env $(workspaces.source.path)/cachi2/output \ + --format env \ + --for-output-dir=/cachi2/output \ + --output $(workspaces.source.path)/cachi2/cachi2.env + + cachi2 --log-level="$LOG_LEVEL" inject-files $(workspaces.source.path)/cachi2/output \ + --for-output-dir=/cachi2/output + workspaces: + - name: source + description: Workspace with the source code, cachi2 artifacts will be stored on the workspace as well + - name: git-basic-auth + description: | + A Workspace containing a .gitconfig and .git-credentials file or username and password. + These will be copied to the user's home before any cachi2 commands are run. Any + other files in this Workspace are ignored. It is strongly recommended + to bind a Secret to this Workspace over other volume types. + optional: true + volumes: + - name: trusted-ca + configMap: + name: $(params.caTrustConfigMapName) + items: + - key: $(params.caTrustConfigMapKey) + path: ca-bundle.crt + optional: true diff --git a/ta-generator/golden/prefetch-dependencies/recipe.yaml b/ta-generator/golden/prefetch-dependencies/recipe.yaml new file mode 100644 index 0000000000..61c40b7382 --- /dev/null +++ b/ta-generator/golden/prefetch-dependencies/recipe.yaml @@ -0,0 +1,33 @@ +--- +add: + - use-source + - create-source + - create-cachi2 +additionalSteps: + - at: 0 + name: skip-ta + image: registry.access.redhat.com/ubi9/ubi-minimal:9.3-1612@sha256:119ac25920c8bb50c8b5fd75dcbca369bf7d1f702b82f3d39663307890f0bf26 + env: + - name: INPUT + value: $(params.input) + - name: SOURCE_ARTIFACT + value: $(params.SOURCE_ARTIFACT) + script: | + if [ -z "${INPUT}" ]; then + mkdir -p /var/workdir/source + mkdir -p /var/workdir/cachi2 + echo "true" > /var/workdir/source/.skip-trusted-artifacts + echo "true" > /var/workdir/cachi2/.skip-trusted-artifacts + echo -n "${SOURCE_ARTIFACT}" > $(results.SOURCE_ARTIFACT.path) + echo -n "" > $(results.CACHI2_ARTIFACT.path) + fi +description: |- + Task that uses Cachi2 to prefetch build dependencies. The fetched dependencies and the + application source code are stored as a trusted artifact in the provided OCI repository. + For additional info on Cachi2, see docs at + https://github.com/containerbuildsystem/cachi2#basic-usage. +preferStepTemplate: true +removeWorkspaces: + - source +replacements: + workspaces.source.path: /var/workdir diff --git a/ta-generator/golden/prefetch-dependencies/ta.yaml b/ta-generator/golden/prefetch-dependencies/ta.yaml new file mode 100644 index 0000000000..cc100cda23 --- /dev/null +++ b/ta-generator/golden/prefetch-dependencies/ta.yaml @@ -0,0 +1,178 @@ +--- +apiVersion: tekton.dev/v1 +kind: Task +metadata: + labels: + app.kubernetes.io/version: "0.1" + annotations: + tekton.dev/pipelines.minVersion: "0.12.1" + tekton.dev/tags: "image-build, hacbs" + name: prefetch-dependencies-oci-ta +spec: + description: |- + Task that uses Cachi2 to prefetch build dependencies. The fetched dependencies and the + application source code are stored as a trusted artifact in the provided OCI repository. + For additional info on Cachi2, see docs at + https://github.com/containerbuildsystem/cachi2#basic-usage. + params: + - description: Configures project packages that will have their dependencies prefetched. + name: input + - description: The Trusted Artifact URI pointing to the artifact with the application source code. + name: SOURCE_ARTIFACT + type: string + - description: The OCI repository where the Trusted Artifacts are stored. + name: ociStorage + type: string + - description: >- + Expiration date for the trusted artifacts created in the OCI repository. An empty string means + the artifacts do not expire. + name: ociArtifactExpiresAfter + type: string + default: "" + - description: > + Enable in-development package managers. WARNING: the behavior may change at any time without + notice. Use at your own risk. + name: dev-package-managers + default: "false" + - description: Set cachi2 log level (debug, info, warning, error) + name: log-level + default: "info" + - name: caTrustConfigMapName + type: string + description: The name of the ConfigMap to read CA bundle data from. + default: trusted-ca + - name: caTrustConfigMapKey + type: string + description: The name of the key in the ConfigMap that contains the CA bundle data. + default: ca-bundle.crt + results: + - description: The Trusted Artifact URI pointing to the artifact with the application source code. + name: SOURCE_ARTIFACT + type: string + - description: The Trusted Artifact URI pointing to the artifact with the prefetched dependencies. + name: CACHI2_ARTIFACT + type: string + stepTemplate: + volumeMounts: + - mountPath: /var/workdir + name: workdir + steps: + - name: skip-ta + image: registry.access.redhat.com/ubi9/ubi-minimal:9.3-1612@sha256:119ac25920c8bb50c8b5fd75dcbca369bf7d1f702b82f3d39663307890f0bf26 + env: + - name: INPUT + value: $(params.input) + - name: SOURCE_ARTIFACT + value: $(params.SOURCE_ARTIFACT) + script: | + if [ -z "${INPUT}" ]; then + mkdir -p /var/workdir/source + mkdir -p /var/workdir/cachi2 + echo "true" > /var/workdir/source/.skip-trusted-artifacts + echo "true" > /var/workdir/cachi2/.skip-trusted-artifacts + echo -n "${SOURCE_ARTIFACT}" > $(results.SOURCE_ARTIFACT.path) + echo -n "" > $(results.CACHI2_ARTIFACT.path) + fi + - image: quay.io/redhat-appstudio/build-trusted-artifacts:latest@sha256:placeholder + name: use-trusted-artifact + args: + - use + - $(params.SOURCE_ARTIFACT)=/var/workdir/source + - image: quay.io/redhat-appstudio/cachi2:0.7.0@sha256:1fc772aa3636fd0b43d62120d832e5913843e028e8cac42814b487c3a0a32bd8 + name: prefetch-dependencies + env: + - name: INPUT + value: $(params.input) + - name: DEV_PACKAGE_MANAGERS + value: $(params.dev-package-managers) + - name: LOG_LEVEL + value: $(params.log-level) + - name: WORKSPACE_GIT_AUTH_BOUND + value: $(workspaces.git-basic-auth.bound) + - name: WORKSPACE_GIT_AUTH_PATH + value: $(workspaces.git-basic-auth.path) + volumeMounts: + - name: trusted-ca + mountPath: /mnt/trusted-ca + readOnly: true + script: | + if [ -z "${INPUT}" ] + then + # Confirm input was provided though it's likely the whole task would be skipped if it wasn't + echo "No prefetch will be performed because no input was provided for cachi2 fetch-deps" + exit 0 + fi + + if [ "$DEV_PACKAGE_MANAGERS" = "true" ]; then + dev_pacman_flag=--dev-package-managers + else + dev_pacman_flag="" + fi + + # Copied from https://github.com/konflux-ci/build-definitions/blob/main/task/git-clone/0.1/git-clone.yaml + if [ "${WORKSPACE_GIT_AUTH_BOUND}" = "true" ] ; then + if [ -f "${WORKSPACE_GIT_AUTH_PATH}/.git-credentials" ] && [ -f "${WORKSPACE_GIT_AUTH_PATH}/.gitconfig" ]; then + cp "${WORKSPACE_GIT_AUTH_PATH}/.git-credentials" "${HOME}/.git-credentials" + cp "${WORKSPACE_GIT_AUTH_PATH}/.gitconfig" "${HOME}/.gitconfig" + # Compatibility with kubernetes.io/basic-auth secrets + elif [ -f "${WORKSPACE_GIT_AUTH_PATH}/username" ] && [ -f "${WORKSPACE_GIT_AUTH_PATH}/password" ]; then + HOSTNAME=$(cd "/var/workdir/source" && git remote get-url origin | awk -F/ '{print $3}') + echo "https://$(cat ${WORKSPACE_GIT_AUTH_PATH}/username):$(cat ${WORKSPACE_GIT_AUTH_PATH}/password)@$HOSTNAME" > "${HOME}/.git-credentials" + echo -e "[credential \"https://$HOSTNAME\"]\n helper = store" > "${HOME}/.gitconfig" + else + echo "Unknown git-basic-auth workspace format" + exit 1 + fi + chmod 400 "${HOME}/.git-credentials" + chmod 400 "${HOME}/.gitconfig" + fi + + ca_bundle=/mnt/trusted-ca/ca-bundle.crt + if [ -f "$ca_bundle" ]; then + echo "INFO: Using mounted CA bundle: $ca_bundle" + cp -vf $ca_bundle /etc/pki/ca-trust/source/anchors + update-ca-trust + fi + + cachi2 --log-level="$LOG_LEVEL" fetch-deps \ + $dev_pacman_flag \ + --source=/var/workdir/source \ + --output=/var/workdir/cachi2/output \ + "${INPUT}" + + cachi2 --log-level="$LOG_LEVEL" generate-env /var/workdir/cachi2/output \ + --format env \ + --for-output-dir=/cachi2/output \ + --output /var/workdir/cachi2/cachi2.env + + cachi2 --log-level="$LOG_LEVEL" inject-files /var/workdir/cachi2/output \ + --for-output-dir=/cachi2/output + - image: quay.io/redhat-appstudio/build-trusted-artifacts:latest@sha256:placeholder + name: create-trusted-artifact + env: + - name: IMAGE_EXPIRES_AFTER + value: $(params.ociArtifactExpiresAfter) + args: + - create + - --store + - $(params.ociStorage) + - $(results.SOURCE_ARTIFACT.path)=/var/workdir/source + - $(results.CACHI2_ARTIFACT.path)=/var/workdir/cachi2 + workspaces: + - name: git-basic-auth + description: | + A Workspace containing a .gitconfig and .git-credentials file or username and password. + These will be copied to the user's home before any cachi2 commands are run. Any + other files in this Workspace are ignored. It is strongly recommended + to bind a Secret to this Workspace over other volume types. + optional: true + volumes: + - name: workdir + emptyDir: {} + - name: trusted-ca + configMap: + name: $(params.caTrustConfigMapName) + items: + - key: $(params.caTrustConfigMapKey) + path: ca-bundle.crt + optional: true diff --git a/ta-generator/golden/sast-snyk-check/base.yaml b/ta-generator/golden/sast-snyk-check/base.yaml new file mode 100644 index 0000000000..8894d28492 --- /dev/null +++ b/ta-generator/golden/sast-snyk-check/base.yaml @@ -0,0 +1,88 @@ +apiVersion: tekton.dev/v1 +kind: Task +metadata: + labels: + app.kubernetes.io/version: "0.1" + annotations: + tekton.dev/pipelines.minVersion: "0.12.1" + tekton.dev/tags: "appstudio, hacbs" + name: sast-snyk-check +spec: + description: >- + Scans source code for security vulnerabilities, including common issues such as SQL injection, cross-site scripting (XSS), and code injection attacks using Snyk Code, a Static Application Security Testing (SAST) tool. + results: + - description: Tekton task test output. + name: TEST_OUTPUT + params: + - name: SNYK_SECRET + description: Name of secret which contains Snyk token. + default: snyk-secret + - name: ARGS + type: string + description: Append arguments. + default: "--all-projects --exclude=test*,vendor,deps" + volumes: + - name: snyk-secret + secret: + secretName: $(params.SNYK_SECRET) + optional: true + steps: + - name: sast-snyk-check + image: quay.io/redhat-appstudio/konflux-test:v1.4.0@sha256:54d49b37c9a2e280d42961a57e4f7a16c171d6b065559f1329b548db85300bea + # per https://kubernetes.io/docs/concepts/containers/images/#imagepullpolicy-defaulting + # the cluster will set imagePullPolicy to IfNotPresent + workingDir: $(workspaces.workspace.path)/hacbs/$(context.task.name) + volumeMounts: + - name: snyk-secret + mountPath: "/etc/secrets" + readOnly: true + env: + - name: SNYK_SECRET + value: $(params.SNYK_SECRET) + - name: ARGS + value: $(params.ARGS) + script: | + #!/usr/bin/env bash + set -euo pipefail + . /utils.sh + trap 'handle_error $(results.TEST_OUTPUT.path)' EXIT + + SNYK_TOKEN_PATH="/etc/secrets/snyk_token" + + if [ -f "${SNYK_TOKEN_PATH}" ] && [ -s "${SNYK_TOKEN_PATH}" ]; then + # SNYK token is provided + SNYK_TOKEN="$(cat ${SNYK_TOKEN_PATH})" + export SNYK_TOKEN + else + to_enable_snyk='[here](https://redhat-appstudio.github.io/docs.appstudio.io/Documentation/main/how-to-guides/testing_applications/enable_snyk_check_for_a_product/)' + note="Task $(context.task.name) skipped: If you wish to use the Snyk code SAST task, please create a secret name snyk-secret with the key "snyk_token" containing the Snyk token by following the steps given ${to_enable_snyk}" + TEST_OUTPUT=$(make_result_json -r SKIPPED -t "$note") + echo "${TEST_OUTPUT}" | tee "$(results.TEST_OUTPUT.path)" + exit 0 + fi + + SNYK_EXIT_CODE=0 + SOURCE_CODE_DIR=$(workspaces.workspace.path)/source + snyk code test $ARGS $SOURCE_CODE_DIR --sarif-file-output=sast_snyk_check_out.json 1>&2>> stdout.txt || SNYK_EXIT_CODE=$? + test_not_skipped=0 + SKIP_MSG="We found 0 supported files" + grep -q "$SKIP_MSG" stdout.txt || test_not_skipped=$? + + if [[ "$SNYK_EXIT_CODE" -eq 0 ]] || [[ "$SNYK_EXIT_CODE" -eq 1 ]]; then + cat sast_snyk_check_out.json + TEST_OUTPUT= + parse_test_output $(context.task.name) sarif sast_snyk_check_out.json || true + + # When the test is skipped, the "SNYK_EXIT_CODE" is 3 and it can also be 3 in some other situation + elif [[ "$test_not_skipped" -eq 0 ]]; then + note="Task $(context.task.name) success: Snyk code test found zero supported files." + ERROR_OUTPUT=$(make_result_json -r SUCCESS -t "$note") + else + echo "sast-snyk-check test failed because of the following issues:" + cat stdout.txt + note="Task $(context.task.name) failed: For details, check Tekton task log." + ERROR_OUTPUT=$(make_result_json -r ERROR -t "$note") + fi + echo "${TEST_OUTPUT:-${ERROR_OUTPUT}}" | tee $(results.TEST_OUTPUT.path) + workspaces: + - name: workspace diff --git a/ta-generator/golden/sast-snyk-check/recipe.yaml b/ta-generator/golden/sast-snyk-check/recipe.yaml new file mode 100644 index 0000000000..3ac22ef99b --- /dev/null +++ b/ta-generator/golden/sast-snyk-check/recipe.yaml @@ -0,0 +1,27 @@ +--- +add: + - use-source +description: >- + Scans source code for security vulnerabilities, including common issues such as SQL injection, + cross-site scripting (XSS), and code injection attacks using Snyk Code, a Static Application + Security Testing (SAST) tool. + + + Follow the steps given + [here](https://redhat-appstudio.github.io/docs.appstudio.io/Documentation/main/how-to-guides/testing_applications/enable_snyk_check_for_a_product/) + to obtain a snyk-token and to enable the snyk task in a Pipeline. + + + The snyk binary used in this Task comes from a container image defined in + https://github.com/konflux-ci/konflux-test + + + See https://snyk.io/product/snyk-code/ and https://snyk.io/ for more information about the snyk + tool. +preferStepTemplate: true +removeWorkspaces: + - workspace +replacements: + workspaces.workspace.path: /var/workdir +regexReplacements: + hacbs/\$\(context.task.name\): source diff --git a/ta-generator/golden/sast-snyk-check/ta.yaml b/ta-generator/golden/sast-snyk-check/ta.yaml new file mode 100644 index 0000000000..66e8e98490 --- /dev/null +++ b/ta-generator/golden/sast-snyk-check/ta.yaml @@ -0,0 +1,114 @@ +--- +apiVersion: tekton.dev/v1 +kind: Task +metadata: + labels: + app.kubernetes.io/version: "0.1" + annotations: + tekton.dev/pipelines.minVersion: "0.12.1" + tekton.dev/tags: "appstudio, hacbs" + name: sast-snyk-check-oci-ta +spec: + description: >- + Scans source code for security vulnerabilities, including common issues such as SQL injection, + cross-site scripting (XSS), and code injection attacks using Snyk Code, a Static Application + Security Testing (SAST) tool. + + + Follow the steps given + [here](https://redhat-appstudio.github.io/docs.appstudio.io/Documentation/main/how-to-guides/testing_applications/enable_snyk_check_for_a_product/) + to obtain a snyk-token and to enable the snyk task in a Pipeline. + + + The snyk binary used in this Task comes from a container image defined in + https://github.com/konflux-ci/konflux-test + + + See https://snyk.io/product/snyk-code/ and https://snyk.io/ for more information about the snyk + tool. + results: + - description: Tekton task test output. + name: TEST_OUTPUT + params: + - name: SOURCE_ARTIFACT + type: string + description: The Trusted Artifact URI pointing to the artifact with the application source code. + - name: SNYK_SECRET + description: Name of secret which contains Snyk token. + default: snyk-secret + - name: ARGS + type: string + description: Append arguments. + default: "--all-projects --exclude=test*,vendor,deps" + volumes: + - name: snyk-secret + secret: + secretName: $(params.SNYK_SECRET) + optional: true + - name: workdir + emptyDir: {} + stepTemplate: + volumeMounts: + - mountPath: /var/workdir + name: workdir + steps: + - name: use-trusted-artifact + image: quay.io/redhat-appstudio/build-trusted-artifacts:latest@sha256:placeholder + args: + - use + - $(params.SOURCE_ARTIFACT)=/var/workdir/source + - name: sast-snyk-check + image: quay.io/redhat-appstudio/konflux-test:v1.4.0@sha256:54d49b37c9a2e280d42961a57e4f7a16c171d6b065559f1329b548db85300bea + workingDir: /var/workdir/source + volumeMounts: + - name: snyk-secret + mountPath: "/etc/secrets" + readOnly: true + env: + - name: SNYK_SECRET + value: $(params.SNYK_SECRET) + - name: ARGS + value: $(params.ARGS) + script: | + #!/usr/bin/env bash + set -euo pipefail + . /utils.sh + trap 'handle_error $(results.TEST_OUTPUT.path)' EXIT + + SNYK_TOKEN_PATH="/etc/secrets/snyk_token" + + if [ -f "${SNYK_TOKEN_PATH}" ] && [ -s "${SNYK_TOKEN_PATH}" ]; then + # SNYK token is provided + SNYK_TOKEN="$(cat ${SNYK_TOKEN_PATH})" + export SNYK_TOKEN + else + to_enable_snyk='[here](https://redhat-appstudio.github.io/docs.appstudio.io/Documentation/main/how-to-guides/testing_applications/enable_snyk_check_for_a_product/)' + note="Task $(context.task.name) skipped: If you wish to use the Snyk code SAST task, please create a secret name snyk-secret with the key "snyk_token" containing the Snyk token by following the steps given ${to_enable_snyk}" + TEST_OUTPUT=$(make_result_json -r SKIPPED -t "$note") + echo "${TEST_OUTPUT}" | tee "$(results.TEST_OUTPUT.path)" + exit 0 + fi + + SNYK_EXIT_CODE=0 + SOURCE_CODE_DIR=/var/workdir/source + snyk code test $ARGS $SOURCE_CODE_DIR --sarif-file-output=sast_snyk_check_out.json 1>&2>> stdout.txt || SNYK_EXIT_CODE=$? + test_not_skipped=0 + SKIP_MSG="We found 0 supported files" + grep -q "$SKIP_MSG" stdout.txt || test_not_skipped=$? + + if [[ "$SNYK_EXIT_CODE" -eq 0 ]] || [[ "$SNYK_EXIT_CODE" -eq 1 ]]; then + cat sast_snyk_check_out.json + TEST_OUTPUT= + parse_test_output $(context.task.name) sarif sast_snyk_check_out.json || true + + # When the test is skipped, the "SNYK_EXIT_CODE" is 3 and it can also be 3 in some other situation + elif [[ "$test_not_skipped" -eq 0 ]]; then + note="Task $(context.task.name) success: Snyk code test found zero supported files." + ERROR_OUTPUT=$(make_result_json -r SUCCESS -t "$note") + else + echo "sast-snyk-check test failed because of the following issues:" + cat stdout.txt + note="Task $(context.task.name) failed: For details, check Tekton task log." + ERROR_OUTPUT=$(make_result_json -r ERROR -t "$note") + fi + echo "${TEST_OUTPUT:-${ERROR_OUTPUT}}" | tee $(results.TEST_OUTPUT.path) diff --git a/ta-generator/golden/source-build/base.yaml b/ta-generator/golden/source-build/base.yaml new file mode 100644 index 0000000000..e24ad9a0ac --- /dev/null +++ b/ta-generator/golden/source-build/base.yaml @@ -0,0 +1,161 @@ +--- +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: source-build + labels: + app.kubernetes.io/version: "0.1" + annotations: + tekton.dev/pipelines.minVersion: "0.12.1" + tekton.dev/tags: "konflux" +spec: + description: Source image build. + params: + - name: BINARY_IMAGE + description: Binary image name from which to generate the source image name. + type: string + - name: BASE_IMAGES + description: >- + By default, the task inspects the SBOM of the binary image to find the base image. + With this parameter, you can override that behavior and pass the base image directly. + The value should be a newline-separated list of images, in the same order as the FROM + instructions specified in a multistage Dockerfile. + type: string + default: "" + results: + - name: BUILD_RESULT + description: Build result. + - name: SOURCE_IMAGE_URL + description: The source image url. + - name: SOURCE_IMAGE_DIGEST + description: The source image digest. + workspaces: + - name: workspace + description: The workspace where source code is included. + volumes: + - name: source-build-work-place + emptyDir: {} + stepTemplate: + env: + - name: BINARY_IMAGE + value: "$(params.BINARY_IMAGE)" + - name: BASE_IMAGES_FILE + value: /var/source-build/base-images.txt + volumeMounts: + - name: source-build-work-place + mountPath: /var/source-build + steps: + - name: get-base-images + image: quay.io/konflux-ci/appstudio-utils:ab6b0b8e40e440158e7288c73aff1cf83a2cc8a9@sha256:24179f0efd06c65d16868c2d7eb82573cce8e43533de6cea14fec3b7446e0b14 + env: + - name: BASE_IMAGES + value: "$(params.BASE_IMAGES)" + script: | + #!/usr/bin/env bash + set -euo pipefail + + if [[ -n "$BASE_IMAGES" ]]; then + echo "BASE_IMAGES param received:" + printf "%s" "$BASE_IMAGES" | tee "$BASE_IMAGES_FILE" + exit + fi + + echo "BASE_IMAGES param is empty, inspecting the SBOM instead" + + raw_inspect=$(skopeo inspect --raw "docker://$BINARY_IMAGE") + if manifest_digest=$(jq -e -r '.manifests[0].digest' <<< "$raw_inspect"); then + # The BINARY_IMAGE is an index image, each manifest in the list has its own SBOM. + # We're gonna assume the base images are the same or similar enough in all the SBOMs. + echo "BINARY_IMAGE ($BINARY_IMAGE) is a manifest list, picking an arbitrary image from the list" + image_without_digest=${BINARY_IMAGE%@*} + image_without_tag=${image_without_digest%:*} + image=${image_without_tag}@${manifest_digest} + else + # The image is a single manifest + image=$BINARY_IMAGE + fi + + for i in {1..5}; do + echo "Downloading SBOM for $image (attempt $i)" + sbom=$(cosign download sbom "$image") && break + [[ "$i" -lt 5 ]] && sleep 1 + done + + if [[ -z "$sbom" ]]; then + echo "Failed to download SBOM after 5 attempts. Proceeding anyway." + echo "WARNING: the source image will not include sources for the base image." + exit 0 + fi + + echo -n "Looking for base image in SBOM" + echo " (.formulation[].components[] with 'konflux:container:is_base_image' property)" + # Note: the SBOM should contain at most one image with the is_base_image property - the + # base image for the last FROM instruction. That is the only base image we care about. + jq -r ' + .formulation[]? + | .components[]? + | select(any(.properties[]?; .name == "konflux:container:is_base_image")) + | (.purl | capture("^pkg:oci/.*?@(?.*?:[a-f0-9]*)")) as $matched + | .name + "@" + $matched.digest + ' <<< "$sbom" | tee "$BASE_IMAGES_FILE" + + - name: build + image: quay.io/konflux-ci/source-container-build:9ad131acf5154d2f280b7b46a1abc543952d325c@sha256:94271c32e4578208ac90308695d2b625d4e932d65f0cdd116b200c39228f5ece + # per https://kubernetes.io/docs/concepts/containers/images/#imagepullpolicy-defaulting + # the cluster will set imagePullPolicy to IfNotPresent + computeResources: + limits: + memory: 2Gi + requests: + memory: 512Mi + cpu: 250m + workingDir: "/var/source-build" + securityContext: + runAsUser: 0 + capabilities: + add: + - SETFCAP + env: + - name: SOURCE_DIR + value: "$(workspaces.workspace.path)/source" + - name: RESULT_FILE + value: "$(results.BUILD_RESULT.path)" + - name: CACHI2_ARTIFACTS_DIR + value: "$(workspaces.workspace.path)/cachi2" + - name: RESULT_SOURCE_IMAGE_URL + value: "$(results.SOURCE_IMAGE_URL.path)" + - name: RESULT_SOURCE_IMAGE_DIGEST + value: "$(results.SOURCE_IMAGE_DIGEST.path)" + - name: WS_BUILD_RESULT_FILE + value: "$(workspaces.workspace.path)/source_build_result.json" + script: | + #!/usr/bin/env bash + set -euo pipefail + + app_dir=/opt/source_build + registry_allowlist=" + registry.access.redhat.com + registry.redhat.io + " + + ## This is needed for the builds performed by the rpm-ostree task + ## otherwise, we can see this error: + ## "fatal: detected dubious ownership in repository at '/workspace/workspace/source'" + ## + git config --global --add safe.directory $SOURCE_DIR + + base_images=$(if [[ -f "$BASE_IMAGES_FILE" ]]; then cat "$BASE_IMAGES_FILE"; fi) + + ${app_dir}/appenv/bin/python3 ${app_dir}/source_build.py \ + --output-binary-image "$BINARY_IMAGE" \ + --workspace /var/source-build \ + --source-dir "$SOURCE_DIR" \ + --base-images "$base_images" \ + --write-result-to "$RESULT_FILE" \ + --cachi2-artifacts-dir "$CACHI2_ARTIFACTS_DIR" \ + --registry-allowlist="$registry_allowlist" + + cat "$RESULT_FILE" | jq -j ".image_url" >"$RESULT_SOURCE_IMAGE_URL" + cat "$RESULT_FILE" | jq -j ".image_digest" >"$RESULT_SOURCE_IMAGE_DIGEST" + + cp "$RESULT_FILE" "$WS_BUILD_RESULT_FILE" diff --git a/ta-generator/golden/source-build/recipe.yaml b/ta-generator/golden/source-build/recipe.yaml new file mode 100644 index 0000000000..f9d27410ba --- /dev/null +++ b/ta-generator/golden/source-build/recipe.yaml @@ -0,0 +1,14 @@ +--- +add: + - use-source + - use-cachi2 +removeWorkspaces: + - workspace +removeVolumes: + - source-build-work-place +regexReplacements: + \/var\/source-build: /var/workdir + \/workspace\/workspace: /var/workdir +preferStepTemplate: true +replacements: + workspaces.workspace.path: /var/workdir diff --git a/ta-generator/golden/source-build/ta.yaml b/ta-generator/golden/source-build/ta.yaml new file mode 100644 index 0000000000..efea0e4ebb --- /dev/null +++ b/ta-generator/golden/source-build/ta.yaml @@ -0,0 +1,171 @@ +--- +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: source-build-oci-ta + annotations: + tekton.dev/pipelines.minVersion: 0.12.1 + tekton.dev/tags: konflux + labels: + app.kubernetes.io/version: "0.1" +spec: + description: Source image build. + params: + - name: BASE_IMAGES + description: By default, the task inspects the SBOM of the binary image + to find the base image. With this parameter, you can override that + behavior and pass the base image directly. The value should be a newline-separated + list of images, in the same order as the FROM instructions specified + in a multistage Dockerfile. + type: string + default: "" + - name: BINARY_IMAGE + description: Binary image name from which to generate the source image + name. + type: string + - name: CACHI2_ARTIFACT + description: The Trusted Artifact URI pointing to the artifact with + the prefetched dependencies. + type: string + default: "" + - name: SOURCE_ARTIFACT + description: The Trusted Artifact URI pointing to the artifact with + the application source code. + type: string + results: + - name: BUILD_RESULT + description: Build result. + - name: SOURCE_IMAGE_DIGEST + description: The source image digest. + - name: SOURCE_IMAGE_URL + description: The source image url. + volumes: + - name: workdir + emptyDir: {} + stepTemplate: + env: + - name: BASE_IMAGES_FILE + value: /var/workdir/base-images.txt + - name: BINARY_IMAGE + value: $(params.BINARY_IMAGE) + volumeMounts: + - mountPath: /var/workdir + name: workdir + steps: + - name: use-trusted-artifact + image: quay.io/redhat-appstudio/build-trusted-artifacts:latest@sha256:placeholder + args: + - use + - $(params.SOURCE_ARTIFACT)=/var/workdir/source + - $(params.CACHI2_ARTIFACT)=/var/workdir/cachi2 + - name: get-base-images + image: quay.io/konflux-ci/appstudio-utils:ab6b0b8e40e440158e7288c73aff1cf83a2cc8a9@sha256:24179f0efd06c65d16868c2d7eb82573cce8e43533de6cea14fec3b7446e0b14 + env: + - name: BASE_IMAGES + value: $(params.BASE_IMAGES) + script: | + #!/usr/bin/env bash + set -euo pipefail + + if [[ -n "$BASE_IMAGES" ]]; then + echo "BASE_IMAGES param received:" + printf "%s" "$BASE_IMAGES" | tee "$BASE_IMAGES_FILE" + exit + fi + + echo "BASE_IMAGES param is empty, inspecting the SBOM instead" + + raw_inspect=$(skopeo inspect --raw "docker://$BINARY_IMAGE") + if manifest_digest=$(jq -e -r '.manifests[0].digest' <<<"$raw_inspect"); then + # The BINARY_IMAGE is an index image, each manifest in the list has its own SBOM. + # We're gonna assume the base images are the same or similar enough in all the SBOMs. + echo "BINARY_IMAGE ($BINARY_IMAGE) is a manifest list, picking an arbitrary image from the list" + image_without_digest=${BINARY_IMAGE%@*} + image_without_tag=${image_without_digest%:*} + image=${image_without_tag}@${manifest_digest} + else + # The image is a single manifest + image=$BINARY_IMAGE + fi + + for i in {1..5}; do + echo "Downloading SBOM for $image (attempt $i)" + sbom=$(cosign download sbom "$image") && break + [[ "$i" -lt 5 ]] && sleep 1 + done + + if [[ -z "$sbom" ]]; then + echo "Failed to download SBOM after 5 attempts. Proceeding anyway." + echo "WARNING: the source image will not include sources for the base image." + exit 0 + fi + + echo -n "Looking for base image in SBOM" + echo " (.formulation[].components[] with 'konflux:container:is_base_image' property)" + # Note: the SBOM should contain at most one image with the is_base_image property - the + # base image for the last FROM instruction. That is the only base image we care about. + jq -r ' + .formulation[]? + | .components[]? + | select(any(.properties[]?; .name == "konflux:container:is_base_image")) + | (.purl | capture("^pkg:oci/.*?@(?.*?:[a-f0-9]*)")) as $matched + | .name + "@" + $matched.digest + ' <<<"$sbom" | tee "$BASE_IMAGES_FILE" + - name: build + image: quay.io/konflux-ci/source-container-build:9ad131acf5154d2f280b7b46a1abc543952d325c@sha256:94271c32e4578208ac90308695d2b625d4e932d65f0cdd116b200c39228f5ece + workingDir: /var/workdir + env: + - name: SOURCE_DIR + value: /var/workdir/source + - name: RESULT_FILE + value: $(results.BUILD_RESULT.path) + - name: CACHI2_ARTIFACTS_DIR + value: /var/workdir/cachi2 + - name: RESULT_SOURCE_IMAGE_URL + value: $(results.SOURCE_IMAGE_URL.path) + - name: RESULT_SOURCE_IMAGE_DIGEST + value: $(results.SOURCE_IMAGE_DIGEST.path) + - name: WS_BUILD_RESULT_FILE + value: /var/workdir/source_build_result.json + script: | + #!/usr/bin/env bash + set -euo pipefail + + app_dir=/opt/source_build + registry_allowlist=" + registry.access.redhat.com + registry.redhat.io + " + + ## This is needed for the builds performed by the rpm-ostree task + ## otherwise, we can see this error: + ## "fatal: detected dubious ownership in repository at '/var/workdir/source'" + ## + git config --global --add safe.directory $SOURCE_DIR + + base_images=$(if [[ -f "$BASE_IMAGES_FILE" ]]; then cat "$BASE_IMAGES_FILE"; fi) + + ${app_dir}/appenv/bin/python3 ${app_dir}/source_build.py \ + --output-binary-image "$BINARY_IMAGE" \ + --workspace /var/workdir \ + --source-dir "$SOURCE_DIR" \ + --base-images "$base_images" \ + --write-result-to "$RESULT_FILE" \ + --cachi2-artifacts-dir "$CACHI2_ARTIFACTS_DIR" \ + --registry-allowlist="$registry_allowlist" + + cat "$RESULT_FILE" | jq -j ".image_url" >"$RESULT_SOURCE_IMAGE_URL" + cat "$RESULT_FILE" | jq -j ".image_digest" >"$RESULT_SOURCE_IMAGE_DIGEST" + + cp "$RESULT_FILE" "$WS_BUILD_RESULT_FILE" + computeResources: + limits: + memory: 2Gi + requests: + cpu: 250m + memory: 512Mi + securityContext: + capabilities: + add: + - SETFCAP + runAsUser: 0 diff --git a/ta-generator/main.go b/ta-generator/main.go new file mode 100644 index 0000000000..06ab310fc6 --- /dev/null +++ b/ta-generator/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "os" + "path" + "strings" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s path/to/recipe.yaml\n", os.Args[0]) + os.Exit(1) + } + + recipePath := os.Args[1] + + recipe := expectValue(readRecipe(recipePath)) + + task := expectValue(readTask(recipe.Base)) + + taDir := path.Dir(recipePath) + + taTaskPath := path.Join(taDir, path.Base(path.Dir(taDir))+".yaml") + + if _, err := os.Stat(taTaskPath); err == nil { + existing := expectValue(readTask(taTaskPath)) + for _, step := range existing.Spec.Steps { + if strings.Contains(step.Image, "/build-trusted-artifacts:") { + image = step.Image + break + } + } + } + + expect(perform(task, recipe)) + + expect(writeTask(task, os.Stdout)) +} diff --git a/ta-generator/main_test.go b/ta-generator/main_test.go new file mode 100644 index 0000000000..a990cf471c --- /dev/null +++ b/ta-generator/main_test.go @@ -0,0 +1,81 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "os" + "path" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/zregvart/tkn-fmt/format" +) + +func TestGolden(t *testing.T) { + dirs, err := os.ReadDir("golden") + if err != nil { + t.Fatal(err) + } + + resolveImage = func() string { + return "quay.io/redhat-appstudio/build-trusted-artifacts:latest@sha256:resolved" + } + + for _, dir := range dirs { + t.Run(dir.Name(), func(t *testing.T) { + task, err := readTask(path.Join("golden", dir.Name(), "base.yaml")) + if err != nil { + t.Fatal(err) + } + + recipe, err := readRecipe(path.Join("golden", dir.Name(), "recipe.yaml")) + if err != nil { + t.Fatal(err) + } + + switch dir.Name() { + case "buildah": + image = "" // force resolve + case "git-clone": + image = "quay.io/redhat-appstudio/build-trusted-artifacts:latest@sha256:existing" // use existing + default: + image = "quay.io/redhat-appstudio/build-trusted-artifacts:latest@sha256:placeholder" + } + + if err := perform(task, recipe); err != nil { + t.Fatal(err) + } + + got := bytes.Buffer{} + if err := writeTask(task, &got); err != nil { + t.Fatal(err) + } + + ta, err := os.Open(path.Join("golden", dir.Name(), "ta.yaml")) + if err != nil { + t.Fatal(err) + } + + golden := bytes.Buffer{} + if err := format.Format(ta, &golden); err != nil { + t.Fatal(err) + } + + expected := golden.String() + + if diff := cmp.Diff(expected, got.String()); diff != "" { + failure := fmt.Errorf("%s mismatch (-want +got):\n%s", dir.Name(), diff) + if err := os.WriteFile(path.Join(path.Join("golden", dir.Name(), "got")), got.Bytes(), 0644); err != nil { + failure = errors.Join(failure, err) + } + if err := os.WriteFile(path.Join(path.Join("golden", dir.Name(), "expected")), golden.Bytes(), 0644); err != nil { + failure = errors.Join(failure, err) + } + + t.Error(failure) + + } + }) + } +} diff --git a/ta-generator/recipe.go b/ta-generator/recipe.go new file mode 100644 index 0000000000..2d2691d3dd --- /dev/null +++ b/ta-generator/recipe.go @@ -0,0 +1,67 @@ +package main + +import ( + "os" + "path/filepath" + "slices" + "sort" + + pipeline "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + core "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" +) + +type AdditionalStep struct { + pipeline.Step + At int `json:"at"` +} + +type Recipe struct { + Add []string `json:"add"` + AddEnvironment []core.EnvVar `json:"addEnvironment"` + AdditionalSteps []AdditionalStep `json:"additionalSteps"` + AddParams pipeline.ParamSpecs `json:"addParams"` + AddResult []pipeline.TaskResult `json:"addResult"` + AddVolume []core.Volume `json:"addVolume"` + AddVolumeMount []core.VolumeMount `json:"addVolumeMount"` + Base string `json:"base"` + Description string `json:"description"` + DisplaySuffix string `json:"displaySuffix"` + PreferStepTemplate bool `json:"preferStepTemplate"` + RegexReplacements map[string]string `json:"regexReplacements"` + RemoveParams []string `json:"removeParams"` + RemoveVolumes []string `json:"removeVolumes"` + RemoveWorkspaces []string `json:"removeWorkspaces"` + Replacements map[string]string `json:"replacements"` + Suffix string `json:"suffix"` + createCachi2 bool + createSource bool + useCachi2 bool + useSource bool +} + +func readRecipe(path string) (*Recipe, error) { + b := expectValue(os.ReadFile(path)) // #nosec G304 -- the file is passed in by the user, this is expected + + // with defaults + recipe := Recipe{ + Suffix: "-oci-ta", + DisplaySuffix: " oci trusted artifacts", + } + + if err := yaml.Unmarshal(b, &recipe); err != nil { + return nil, err + } + + sort.Strings(recipe.Add) + _, recipe.createCachi2 = slices.BinarySearch(recipe.Add, "create-cachi2") + _, recipe.createSource = slices.BinarySearch(recipe.Add, "create-source") + _, recipe.useCachi2 = slices.BinarySearch(recipe.Add, "use-cachi2") + _, recipe.useSource = slices.BinarySearch(recipe.Add, "use-source") + + if !filepath.IsAbs(recipe.Base) { + recipe.Base = filepath.Join(filepath.Dir(path), recipe.Base) + } + + return &recipe, nil +} diff --git a/ta-generator/shell.go b/ta-generator/shell.go new file mode 100644 index 0000000000..df8abb7752 --- /dev/null +++ b/ta-generator/shell.go @@ -0,0 +1,136 @@ +package main + +import ( + "regexp" + "slices" + "sort" + "strings" + + "mvdan.cc/sh/v3/fileutil" + "mvdan.cc/sh/v3/syntax" +) + +var ( + parser = syntax.NewParser(syntax.KeepComments(true)) + printer = syntax.NewPrinter(syntax.KeepPadding(true), syntax.Indent(2)) +) + +func removeEnvUse(f *syntax.File, name string) []*syntax.Stmt { + modified := make([]*syntax.Stmt, 0, len(f.Stmts)) + syntax.Walk(f, func(node syntax.Node) bool { + if p, ok := node.(*syntax.ParamExp); ok { + // parameter expansion, e.g. ${name} + if p.Param.Value == name { + // remove every line starting from the line where the ${name} was used + start := p.Param.Pos().Line() + + var end uint = 0 + for _, s := range f.Stmts { + line := s.Pos().Line() + if line == start { + if ifstmt, ok := s.Cmd.(*syntax.IfClause); ok { + // if the if statement is at the start line, remove + // the lines till the corresponding `fi` statement + end = ifstmt.FiPos.Line() + } + if assign, ok := s.Cmd.(*syntax.CallExpr); ok { + // remove the whole assignment + end = assign.End().Line() + } + } + + if line < start || (line > end || end == 0) { + // add only the lines that are not in the start-end segment + modified = append(modified, s) + } + } + } + } + + return true + }) + + if len(modified) == 0 { + // if the environment variable is not found, the modified slice will be empty + return f.Stmts + } + + return modified +} + +func removeUnusedFunctions(f *syntax.File) []*syntax.Stmt { + used := make([]string, 0, 10) // includes used functions and other calls (echo, printf...) + syntax.Walk(f, func(node syntax.Node) bool { + if c, ok := node.(*syntax.CallExpr); ok && len(c.Args) > 0 { + // first argument of a call statement is the name + used = append(used, c.Args[0].Lit()) + } + + return true + }) + + sort.Strings(used) + + forRemoval := make([]struct{ start, end uint }, 0, 10) + syntax.Walk(f, func(node syntax.Node) bool { + if fn, ok := node.(*syntax.FuncDecl); ok { + if _, found := slices.BinarySearch(used, fn.Name.Value); !found { + // we found a function declared and unused + forRemoval = append(forRemoval, struct{ start, end uint }{fn.Pos().Line(), fn.End().Line()}) + } + } + return true + }) + + modified := make([]*syntax.Stmt, 0, len(f.Stmts)) + for _, s := range f.Stmts { + line := s.Position.Line() + remove := false + for _, r := range forRemoval { + if remove = line >= r.start && line <= r.end; remove { + // found lines comprising a unused function declaration + break + } + } + + if !remove { + modified = append(modified, s) + } + } + + return modified +} + +func replaceLiterals(f *syntax.File, rx map[*regexp.Regexp]string) []*syntax.Stmt { + syntax.Walk(f, func(n syntax.Node) bool { + if l, ok := n.(*syntax.Lit); ok { + l.Value = applyRegexReplacements(l.Value, rx) + } + if s, ok := n.(*syntax.Stmt); ok { + for i := range s.Comments { + s.Comments[i].Text = applyRegexReplacements(s.Comments[i].Text, rx) + } + } + return true + }) + + return f.Stmts +} + +func isShell(script string) bool { + if script == "" { + return false + } + + // fileutil.Shebang returns "" if the shebang is not shell + if shebang := fileutil.Shebang([]byte(script)); shebang != "" { + return true + } + + // folk don't add shebangs so missing one defaults to shell + if !strings.HasPrefix(script, "#!") { + return true + } + + return false +} diff --git a/ta-generator/ta.go b/ta-generator/ta.go new file mode 100644 index 0000000000..19e1c5760a --- /dev/null +++ b/ta-generator/ta.go @@ -0,0 +1,350 @@ +package main + +import ( + "bytes" + "fmt" + "regexp" + "slices" + "strings" + + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" + pipeline "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + core "k8s.io/api/core/v1" +) + +var ( + image = "" + + resolveImage = func() string { + ref := name.MustParseReference("quay.io/redhat-appstudio/build-trusted-artifacts:latest") + + desc, err := remote.Head(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain)) + if err != nil { + panic(err) + } + + return "quay.io/redhat-appstudio/build-trusted-artifacts:latest@" + desc.Digest.String() + } +) + +func ensureImage() { + if image != "" { + return + } + + image = resolveImage() +} + +func perform(task *pipeline.Task, recipe *Recipe) error { + ensureImage() + + sourceResult := pipeline.TaskResult{ + Name: "SOURCE_ARTIFACT", + Description: "The Trusted Artifact URI pointing to the artifact with the application source code.", + Type: pipeline.ResultsTypeString, + } + + cachi2Result := pipeline.TaskResult{ + Name: "CACHI2_ARTIFACT", + Description: "The Trusted Artifact URI pointing to the artifact with the prefetched dependencies.", + Type: pipeline.ResultsTypeString, + } + + useSourceParam := pipeline.ParamSpec{ + Name: "SOURCE_ARTIFACT", + Type: pipeline.ParamTypeString, + Description: "The Trusted Artifact URI pointing to the artifact with the application source code.", + } + + useCachi2Param := pipeline.ParamSpec{ + Name: "CACHI2_ARTIFACT", + Type: pipeline.ParamTypeString, + Description: "The Trusted Artifact URI pointing to the artifact with the prefetched dependencies.", + Default: &pipeline.ParamValue{Type: pipeline.ParamTypeString, StringVal: ""}, + } + + createParams := pipeline.ParamSpecs{ + { + Name: "ociStorage", + Description: "The OCI repository where the Trusted Artifacts are stored.", + Type: pipeline.ParamTypeString, + }, + { + Name: "ociArtifactExpiresAfter", + Description: "Expiration date for the trusted artifacts created in the OCI repository. An empty string means the artifacts do not expire.", + Type: pipeline.ParamTypeString, + Default: &pipeline.ParamValue{Type: pipeline.ParamTypeString, StringVal: ""}, + }, + } + + task.Name += recipe.Suffix + + if recipe.Description != "" { + task.Spec.Description = recipe.Description + } + + if _, ok := task.Annotations["tekton.dev/displayName"]; ok { + task.Annotations["tekton.dev/displayName"] += recipe.DisplaySuffix + } + + task.Spec.Params = slices.DeleteFunc(task.Spec.Params, func(ps pipeline.ParamSpec) bool { + for _, rm := range recipe.RemoveParams { + if ps.Name == rm { + return true + } + } + + return false + }) + + task.Spec.Workspaces = slices.DeleteFunc(task.Spec.Workspaces, func(wd pipeline.WorkspaceDeclaration) bool { + for _, rm := range recipe.RemoveWorkspaces { + if wd.Name == rm { + return true + } + } + return false + }) + if len(task.Spec.Workspaces) == 0 { + task.Spec.Workspaces = nil + } + + task.Spec.Volumes = slices.DeleteFunc(task.Spec.Volumes, func(v core.Volume) bool { + for _, rm := range recipe.RemoveVolumes { + if v.Name == rm { + return true + } + } + return false + }) + + task.Spec.Params = append(task.Spec.Params, recipe.AddParams...) + + if recipe.useSource { + task.Spec.Params = append(task.Spec.Params, useSourceParam) + } + + if recipe.useCachi2 { + task.Spec.Params = append(task.Spec.Params, useCachi2Param) + } + + if recipe.createSource || recipe.createCachi2 { + task.Spec.Params = append(task.Spec.Params, createParams...) + } + + if len(recipe.AddResult) == 0 { + if recipe.createSource { + recipe.AddResult = append(recipe.AddResult, sourceResult) + } + if recipe.createCachi2 { + recipe.AddResult = append(recipe.AddResult, cachi2Result) + } + } + task.Spec.Results = append(task.Spec.Results, recipe.AddResult...) + + if len(recipe.AddVolume) == 0 { + recipe.AddVolume = []core.Volume{{ + Name: "workdir", + VolumeSource: core.VolumeSource{ + EmptyDir: &core.EmptyDirVolumeSource{}, + }, + }} + } + task.Spec.Volumes = append(task.Spec.Volumes, recipe.AddVolume...) + + workdirVolumeMount := core.VolumeMount{ + Name: "workdir", + MountPath: "/var/workdir", + } + if len(recipe.AddVolumeMount) == 0 { + recipe.AddVolumeMount = []core.VolumeMount{workdirVolumeMount} + } + + removeEnv := func(env *[]string) func(core.EnvVar) bool { + return func(e core.EnvVar) bool { + for _, rm := range recipe.RemoveParams { + if strings.Contains(e.Value, "$(params."+rm+")") { + *env = append(*env, e.Name) + return true + } + } + + for _, rm := range recipe.RemoveWorkspaces { + if strings.Contains(e.Value, "$(workspaces."+rm+".path)") { + *env = append(*env, e.Name) + return true + } + } + + return false + } + } + + rx := map[*regexp.Regexp]string{} + for old, new := range recipe.RegexReplacements { + ex, err := regexp.Compile(old) + if err != nil { + panic(err) + } + rx[ex] = new + } + + templateEnv := make([]string, 0, 5) + if task.Spec.StepTemplate != nil || recipe.PreferStepTemplate { + if task.Spec.StepTemplate == nil { + task.Spec.StepTemplate = &pipeline.StepTemplate{} + } + task.Spec.StepTemplate.VolumeMounts = slices.DeleteFunc(task.Spec.StepTemplate.VolumeMounts, func(vm core.VolumeMount) bool { + for _, rm := range recipe.RemoveWorkspaces { + if vm.Name == rm { + return true + } + } + + for _, rm := range recipe.RemoveVolumes { + if vm.Name == rm { + return true + } + } + + return false + }) + + task.Spec.StepTemplate.VolumeMounts = append(task.Spec.StepTemplate.VolumeMounts, recipe.AddVolumeMount...) + + task.Spec.StepTemplate.Env = slices.DeleteFunc(task.Spec.StepTemplate.Env, removeEnv(&templateEnv)) + + for i := range task.Spec.StepTemplate.Env { + task.Spec.StepTemplate.Env[i].Value = applyReplacements(task.Spec.StepTemplate.Env[i].Value, recipe.Replacements) + task.Spec.StepTemplate.Env[i].Value = applyRegexReplacements(task.Spec.StepTemplate.Env[i].Value, rx) + } + + task.Spec.StepTemplate.WorkingDir = applyReplacements(task.Spec.StepTemplate.WorkingDir, recipe.Replacements) + task.Spec.StepTemplate.WorkingDir = applyRegexReplacements(task.Spec.StepTemplate.WorkingDir, rx) + } + + for i := range task.Spec.Steps { + env := make([]string, 0, 5) + + for j := range task.Spec.Steps[i].Env { + task.Spec.Steps[i].Env[j].Value = applyReplacements(task.Spec.Steps[i].Env[j].Value, recipe.Replacements) + task.Spec.Steps[i].Env[j].Value = applyRegexReplacements(task.Spec.Steps[i].Env[j].Value, rx) + } + + task.Spec.Steps[i].Env = slices.DeleteFunc(task.Spec.Steps[i].Env, removeEnv(&env)) + + task.Spec.Steps[i].Env = append(task.Spec.Steps[i].Env, recipe.AddEnvironment...) + + if task.Spec.StepTemplate == nil { + task.Spec.Steps[i].VolumeMounts = append(task.Spec.Steps[i].VolumeMounts, recipe.AddVolumeMount...) + } + + task.Spec.Steps[i].VolumeMounts = slices.DeleteFunc(task.Spec.Steps[i].VolumeMounts, func(vm core.VolumeMount) bool { + for _, rm := range recipe.RemoveVolumes { + if vm.Name == rm { + return true + } + } + + return false + }) + + if len(task.Spec.Steps[i].VolumeMounts) == 0 { + task.Spec.Steps[i].VolumeMounts = nil + } + + task.Spec.Steps[i].WorkingDir = applyReplacements(task.Spec.Steps[i].WorkingDir, recipe.Replacements) + task.Spec.Steps[i].WorkingDir = applyRegexReplacements(task.Spec.Steps[i].WorkingDir, rx) + + if !isShell(task.Spec.Steps[i].Script) { + continue + } + + if len(recipe.Replacements) > 0 { + task.Spec.Steps[i].Script = applyReplacements(task.Spec.Steps[i].Script, recipe.Replacements) + } + + r := strings.NewReader(task.Spec.Steps[i].Script) + f, err := parser.Parse(r, task.Spec.Steps[i].Name+"_script.sh") + if err != nil { + return err + } + + for _, rm := range templateEnv { + f.Stmts = removeEnvUse(f, rm) + } + for _, rm := range env { + f.Stmts = removeEnvUse(f, rm) + } + if len(recipe.RegexReplacements) > 0 { + f.Stmts = replaceLiterals(f, rx) + } + + f.Stmts = removeUnusedFunctions(f) + + buf := bytes.Buffer{} + if err := printer.Print(&buf, f); err != nil { + return err + } + + task.Spec.Steps[i].Script = buf.String() + } + + if recipe.useSource || recipe.useCachi2 { + args := []string{"use"} + + if recipe.useSource { + args = append(args, fmt.Sprintf("$(params.SOURCE_ARTIFACT)=/var/workdir/%s", "source")) + } + + if recipe.useCachi2 { + args = append(args, fmt.Sprintf("$(params.CACHI2_ARTIFACT)=/var/workdir/%s", "cachi2")) + } + + task.Spec.Steps = append([]pipeline.Step{{ + Name: "use-trusted-artifact", + Image: image, + Args: args, + }}, task.Spec.Steps...) + } + if recipe.createSource || recipe.createCachi2 { + args := []string{ + "create", + "--store", + "$(params.ociStorage)", + } + + if recipe.createSource { + args = append(args, "$(results.SOURCE_ARTIFACT.path)=/var/workdir/source") + } + + if recipe.createCachi2 { + args = append(args, "$(results.CACHI2_ARTIFACT.path)=/var/workdir/cachi2") + } + + create := pipeline.Step{ + Name: "create-trusted-artifact", + Image: image, + Env: []core.EnvVar{ + { + Name: "IMAGE_EXPIRES_AFTER", + Value: "$(params.ociArtifactExpiresAfter)", + }, + }, + Args: args, + } + + if task.Spec.StepTemplate == nil && !recipe.PreferStepTemplate { + create.VolumeMounts = []core.VolumeMount{workdirVolumeMount} + } + task.Spec.Steps = append(task.Spec.Steps, create) + } + + for _, additional := range recipe.AdditionalSteps { + task.Spec.Steps = slices.Insert(task.Spec.Steps, additional.At, additional.Step) + } + + return nil +} diff --git a/ta-generator/tekton.go b/ta-generator/tekton.go new file mode 100644 index 0000000000..a99b183a74 --- /dev/null +++ b/ta-generator/tekton.go @@ -0,0 +1,47 @@ +package main + +import ( + "bytes" + "io" + "os" + "regexp" + + pipeline "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + "github.com/tektoncd/pipeline/pkg/substitution" + "github.com/zregvart/tkn-fmt/format" + "sigs.k8s.io/yaml" +) + +func readTask(path string) (*pipeline.Task, error) { + b := expectValue(os.ReadFile(path)) // #nosec G304 -- we want to read the file as Task, nothing to worry about here + b = bytes.TrimLeft(b, "---\n") + task := pipeline.Task{} + return &task, yaml.Unmarshal(b, &task) +} + +func writeTask(task *pipeline.Task, writer io.Writer) error { + if c, ok := writer.(io.Closer); ok { + defer c.Close() + } + + b, err := yaml.Marshal(task) + if err != nil { + return err + } + + buf := bytes.NewBuffer(b) + + return format.Format(buf, writer) +} + +func applyReplacements(in string, replacements map[string]string) string { + return substitution.ApplyReplacements(in, replacements) +} + +func applyRegexReplacements(in string, replacements map[*regexp.Regexp]string) string { + out := in + for ex, new := range replacements { + out = ex.ReplaceAllString(out, new) + } + return out +}